diff options
Diffstat (limited to 'deps/npm/test')
47 files changed, 2629 insertions, 497 deletions
diff --git a/deps/npm/test/broken-under-nyc-and-travis/lifecycle-path.js b/deps/npm/test/broken-under-nyc-and-travis/lifecycle-path.js index a9d32d15b0..61fe229bcf 100644 --- a/deps/npm/test/broken-under-nyc-and-travis/lifecycle-path.js +++ b/deps/npm/test/broken-under-nyc-and-travis/lifecycle-path.js @@ -32,45 +32,125 @@ test('setup', function (t) { }) test('make sure the path is correct, without directory of current node', function (t) { - checkPath(false, t) + checkPath({ + withDirOfCurrentNode: false, + prependNodePathSetting: false + }, t) }) test('make sure the path is correct, with directory of current node', function (t) { - checkPath(true, t) + checkPath({ + withDirOfCurrentNode: true, + prependNodePathSetting: false + }, t) }) -function checkPath (withDirOfCurrentNode, t) { +test('make sure the path is correct, with directory of current node but ignored node path', function (t) { + checkPath({ + withDirOfCurrentNode: true, + prependNodePathSetting: true + }, t) +}) + +test('make sure the path is correct, without directory of current node and automatic detection', function (t) { + checkPath({ + withDirOfCurrentNode: false, + prependNodePathSetting: 'auto' + }, t) +}) + +test('make sure the path is correct, with directory of current node and automatic detection', function (t) { + checkPath({ + withDirOfCurrentNode: true, + prependNodePathSetting: 'auto' + }, t) +}) + +test('make sure the path is correct, without directory of current node and warn-only detection', function (t) { + checkPath({ + withDirOfCurrentNode: false, + prependNodePathSetting: 'warn-only' + }, t) +}) + +test('make sure the path is correct, with directory of current node and warn-only detection', function (t) { + checkPath({ + withDirOfCurrentNode: true, + prependNodePathSetting: 'warn-only' + }, t) +}) + +test('make sure there is no warning with a symlinked node and warn-only detection', { + skip: isWindows && 'symlinks are weird on windows' +}, function (t) { + checkPath({ + withDirOfCurrentNode: false, + extraNode: true, + prependNodePathSetting: 'warn-only', + symlinkNodeInsteadOfCopying: true + }, t) +}) + +test('make sure the path is correct, with directory of current node and warn-only detection and an extra node in path', function (t) { + checkPath({ + withDirOfCurrentNode: false, + extraNode: true, + prependNodePathSetting: 'warn-only' + }, t) +}) + +function checkPath (testconfig, t) { + var withDirOfCurrentNode = testconfig.withDirOfCurrentNode + var prependNodePathSetting = testconfig.prependNodePathSetting + var symlinkedNode = testconfig.symlinkNodeInsteadOfCopying + var extraNode = testconfig.extraNode + var newPATH = PATH var currentNodeExecPath = process.execPath if (withDirOfCurrentNode) { - var newNodeExeDir = path.join(pkg, 'node-bin') + var newNodeExeDir = path.join(pkg, 'node-bin', 'my_bundled_node') mkdirp.sync(newNodeExeDir) - currentNodeExecPath = path.join(newNodeExeDir, 'my_bundled_' + path.basename(process.execPath)) - fs.writeFileSync(currentNodeExecPath, fs.readFileSync(process.execPath)) - fs.chmodSync(currentNodeExecPath, '755') - } else { + currentNodeExecPath = path.join(newNodeExeDir, path.basename(process.execPath)) + rimraf.sync(currentNodeExecPath) + + if (!symlinkedNode) { + fs.writeFileSync(currentNodeExecPath, fs.readFileSync(process.execPath)) + fs.chmodSync(currentNodeExecPath, '755') + } else { + fs.symlinkSync(process.execPath, currentNodeExecPath) + } + } + + if (!withDirOfCurrentNode) { // Ensure that current node interpreter will be found in the PATH, // so the PATH won't be prepended with its parent directory newPATH = [path.dirname(process.execPath), PATH].join(process.platform === 'win32' ? ';' : ':') } + common.npm(['run-script', 'env'], { cwd: pkg, nodeExecPath: currentNodeExecPath, env: { - PATH: newPATH + PATH: newPATH, + npm_config_scripts_prepend_node_path: prependNodePathSetting }, - stdio: [ 0, 'pipe', 2 ] - }, function (er, code, stdout) { + stdio: [ 0, 'pipe', 'pipe' ] + }, function (er, code, stdout, stderr) { if (er) throw er + if (!stderr.match(/^(npm WARN.*)?\n*$/)) console.error(stderr) t.equal(code, 0, 'exit code') var lineMatch = function (line) { return /^PATH=/i.test(line) } // extract just the path value - stdout = stdout.split(/\r?\n/).filter(lineMatch).pop().replace(/^PATH=/, '') + stdout = stdout.split(/\r?\n/) + var observedPath = stdout.filter(lineMatch).pop().replace(/^PATH=/, '') var pathSplit = process.platform === 'win32' ? ';' : ':' var root = path.resolve(__dirname, '../..') - var actual = stdout.split(pathSplit).map(function (p) { + var actual = observedPath.split(pathSplit).map(function (p) { + if (p.indexOf(pkg) === 0) { + p = '{{PKG}}' + p.substr(pkg.length) + } if (p.indexOf(root) === 0) { p = '{{ROOT}}' + p.substr(root.length) } @@ -83,9 +163,36 @@ function checkPath (withDirOfCurrentNode, t) { // get the ones we tacked on, then the system-specific requirements var expectedPaths = ['{{ROOT}}/bin/node-gyp-bin', - '{{ROOT}}/test/broken-under-nyc-and-travis/lifecycle-path/node_modules/.bin'] - if (withDirOfCurrentNode) { - expectedPaths.push('{{ROOT}}/test/broken-under-nyc-and-travis/lifecycle-path/node-bin') + '{{PKG}}/node_modules/.bin'] + + // Check that the behaviour matches the configuration that was actually + // used by the child process, as the coverage tooling may set the + // --scripts-prepend-node-path option on its own. + var realPrependNodePathSetting = stdout.filter(function (line) { + return line.match(/npm_config_scripts_prepend_node_path=(true|auto)/) + }).length > 0 + + if (prependNodePathSetting === 'warn-only') { + if (symlinkedNode) { + t.equal(stderr, '', 'does not spit out a warning') + } else if (withDirOfCurrentNode) { + t.match(stderr, /npm WARN lifecycle/, 'spit out a warning') + t.match(stderr, /npm is using .*node-bin.my_bundled_node(.exe)?/, 'mention the path of the binary npm itself is using.') + if (extraNode) { + var regex = new RegExp( + 'The node binary used for scripts is.*' + + process.execPath.replace(/[/\\]/g, '.')) + t.match(stderr, regex, 'reports the current binary vs conflicting') + } else { + t.match(stderr, /there is no node binary in the current PATH/, 'informs user that there is no node binary in PATH') + } + } else { + t.same(stderr, '') + } + } + + if (withDirOfCurrentNode && realPrependNodePathSetting) { + expectedPaths.push('{{PKG}}/node-bin/my_bundled_node') } var expect = expectedPaths.concat(newPATH.split(pathSplit)).map(function (p) { return p.replace(/\\/g, '/') diff --git a/deps/npm/test/common-tap.js b/deps/npm/test/common-tap.js index 14c1581f78..8fe5b78fd9 100644 --- a/deps/npm/test/common-tap.js +++ b/deps/npm/test/common-tap.js @@ -2,6 +2,7 @@ var fs = require('graceful-fs') var readCmdShim = require('read-cmd-shim') var isWindows = require('../lib/utils/is-windows.js') +var extend = Object.assign || require('util')._extend // cheesy hackaround for test deps (read: nock) that rely on setImmediate if (!global.setImmediate || !require('timers').setImmediate) { @@ -42,6 +43,7 @@ exports.npm = function (cmd, opts, cb) { opts = opts || {} opts.env = opts.env || process.env + if (opts.env._storage) opts.env = opts.env._storage if (!opts.env.npm_config_cache) { opts.env.npm_config_cache = npm_config_cache } @@ -120,3 +122,39 @@ exports.pendIfWindows = function (why) { console.log('not ok 1 # todo ' + why) process.exit(0) } + +exports.newEnv = function () { + return new Environment(process.env) +} + +function Environment (env) { + this._storage = extend({}, env) +} +Environment.prototype = {} + +Environment.prototype.delete = function (key) { + var args = Array.isArray(key) ? key : arguments + var ii + for (ii = 0; ii < args.length; ++ii) { + delete this._storage[args[ii]] + } + return this +} + +Environment.prototype.clone = function () { + return new Environment(this._storage) +} + +Environment.prototype.extend = function (env) { + var self = this + var args = Array.isArray(env) ? env : arguments + var ii + for (ii = 0; ii < args.length; ++ii) { + var arg = args[ii] + if (!arg) continue + Object.keys(arg).forEach(function (name) { + self._storage[name] = arg[name] + }) + } + return this +} diff --git a/deps/npm/test/fixtures/config/package.json b/deps/npm/test/fixtures/config/package.json deleted file mode 100644 index e69de29bb2..0000000000 --- a/deps/npm/test/fixtures/config/package.json +++ /dev/null diff --git a/deps/npm/test/tap/all-package-metadata-cache-stream-unit.js b/deps/npm/test/tap/all-package-metadata-cache-stream-unit.js new file mode 100644 index 0000000000..51be836769 --- /dev/null +++ b/deps/npm/test/tap/all-package-metadata-cache-stream-unit.js @@ -0,0 +1,136 @@ +'use strict' + +require('../common-tap.js') +var test = require('tap').test +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var path = require('path') +var ms = require('mississippi') +var Tacks = require('tacks') +var File = Tacks.File + +var _createCacheEntryStream = require('../../lib/search/all-package-metadata.js')._createCacheEntryStream + +var PKG_DIR = path.resolve(__dirname, 'create-cache-entry-stream') +var CACHE_DIR = path.resolve(PKG_DIR, 'cache') + +function setup () { + mkdirp.sync(CACHE_DIR) +} + +function cleanup () { + rimraf.sync(PKG_DIR) +} + +test('createCacheEntryStream basic', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + var fixture = new Tacks(File({ + '_updated': 1234, + bar: { + name: 'bar', + version: '1.0.0' + }, + foo: { + name: 'foo', + version: '1.0.0' + } + })) + fixture.create(cachePath) + _createCacheEntryStream(cachePath, function (err, stream, latest) { + if (err) throw err + t.equals(latest, 1234, '`latest` correctly extracted') + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + if (err) throw err + t.deepEquals(results, [{ + name: 'bar', + version: '1.0.0' + }, { + name: 'foo', + version: '1.0.0' + }]) + cleanup() + t.done() + }) + }) +}) + +test('createCacheEntryStream empty cache', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + var fixture = new Tacks(File({})) + fixture.create(cachePath) + _createCacheEntryStream(cachePath, function (err, stream, latest) { + t.ok(err, 'returned an error because there was no _updated') + t.match(err.message, /Empty or invalid stream/, 'useful error message') + t.notOk(stream, 'no stream returned') + t.notOk(latest, 'no latest returned') + cleanup() + t.done() + }) +}) + +test('createCacheEntryStream no entry cache', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + var fixture = new Tacks(File({ + '_updated': 1234 + })) + fixture.create(cachePath) + _createCacheEntryStream(cachePath, function (err, stream, latest) { + if (err) throw err + t.equals(latest, 1234, '`latest` correctly extracted') + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + if (err) throw err + t.deepEquals(results, [], 'no results') + cleanup() + t.done() + }) + }) +}) + +test('createCacheEntryStream missing cache', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + _createCacheEntryStream(cachePath, function (err, stream, latest) { + t.ok(err, 'returned an error because there was no cache') + t.equals(err.code, 'ENOENT', 'useful error message') + t.notOk(stream, 'no stream returned') + t.notOk(latest, 'no latest returned') + cleanup() + t.done() + }) +}) + +test('createCacheEntryStream bad syntax', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + var fixture = new Tacks(File('{"_updated": 1234, uh oh')) + fixture.create(cachePath) + _createCacheEntryStream(cachePath, function (err, stream, latest) { + if (err) throw err + t.equals(latest, 1234, '`latest` correctly extracted') + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + t.ok(err, 'stream errored') + t.match(err.message, /Invalid JSON/i, 'explains there\'s a syntax error') + t.deepEquals(results, [], 'no results') + cleanup() + t.done() + }) + }) +}) diff --git a/deps/npm/test/tap/all-package-metadata-entry-stream-unit.js b/deps/npm/test/tap/all-package-metadata-entry-stream-unit.js new file mode 100644 index 0000000000..0e02f84824 --- /dev/null +++ b/deps/npm/test/tap/all-package-metadata-entry-stream-unit.js @@ -0,0 +1,186 @@ +'use strict' + +var common = require('../common-tap.js') +var npm = require('../../') +var test = require('tap').test +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var path = require('path') +var mr = require('npm-registry-mock') +var ms = require('mississippi') +var Tacks = require('tacks') +var File = Tacks.File + +var _createEntryStream = require('../../lib/search/all-package-metadata.js')._createEntryStream + +var ALL = common.registry + '/-/all' +var PKG_DIR = path.resolve(__dirname, 'create-entry-update-stream') +var CACHE_DIR = path.resolve(PKG_DIR, 'cache') + +var server + +function setup () { + mkdirp.sync(CACHE_DIR) +} + +function cleanup () { + rimraf.sync(PKG_DIR) +} + +test('setup', function (t) { + mr({port: common.port, throwOnUnmatched: true}, function (err, s) { + t.ifError(err, 'registry mocked successfully') + npm.load({ cache: CACHE_DIR, registry: common.registry }, function (err) { + t.ifError(err, 'npm loaded successfully') + server = s + t.pass('all set up') + t.done() + }) + }) +}) + +test('createEntryStream full request', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + var dataTime = +(new Date()) + server.get('/-/all').once().reply(200, { + '_updated': dataTime, + 'bar': { name: 'bar', version: '1.0.0' }, + 'foo': { name: 'foo', version: '1.0.0' } + }, { + date: 1234 // should never be used. + }) + _createEntryStream(cachePath, ALL, {}, 600, function (err, stream, latest, newEntries) { + if (err) throw err + t.equals(latest, dataTime, '`latest` correctly extracted') + t.ok(newEntries, 'new entries need to be written to cache') + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + if (err) throw err + t.deepEquals(results, [{ + name: 'bar', + version: '1.0.0' + }, { + name: 'foo', + version: '1.0.0' + }]) + server.done() + cleanup() + t.end() + }) + }) +}) + +test('createEntryStream cache only', function (t) { + setup() + var now = Date.now() + var cacheTime = now - 100000 + var cachePath = path.join(CACHE_DIR, '.cache.json') + var fixture = new Tacks(File({ + '_updated': cacheTime, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + })) + fixture.create(cachePath) + _createEntryStream(cachePath, ALL, {}, 600, function (err, stream, latest, newEntries) { + if (err) throw err + t.equals(latest, cacheTime, '`latest` is cache time') + t.ok(stream, 'returned a stream') + t.notOk(newEntries, 'cache only means no need to write to cache') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + t.ifError(err, 'stream finished without error') + t.deepEquals( + results.map(function (pkg) { return pkg.name }), + ['bar', 'cool', 'foo', 'other'], + 'packages deduped and sorted' + ) + server.done() + cleanup() + t.end() + }) + }) +}) + +test('createEntryStream merged stream', function (t) { + setup() + var now = Date.now() + var cacheTime = now - 6000000 + server.get('/-/all/since?stale=update_after&startkey=' + cacheTime).once().reply(200, { + 'bar': { name: 'bar', version: '2.0.0' }, + 'car': { name: 'car', version: '1.0.0' }, + 'foo': { name: 'foo', version: '1.0.0' } + }, { + date: (new Date(now)).toISOString() + }) + var cachePath = path.join(CACHE_DIR, '.cache.json') + var fixture = new Tacks(File({ + '_updated': cacheTime, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + })) + fixture.create(cachePath) + _createEntryStream(cachePath, ALL, {}, 600, function (err, stream, latest, newEntries) { + if (err) throw err + t.equals(latest, now, '`latest` correctly extracted from header') + t.ok(stream, 'returned a stream') + t.ok(newEntries, 'cache update means entries should be written') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + t.ifError(err, 'stream finished without error') + t.deepEquals( + results.map(function (pkg) { return pkg.name }), + ['bar', 'car', 'cool', 'foo', 'other'], + 'packages deduped and sorted' + ) + t.deepEquals(results[0], { + name: 'bar', + version: '2.0.0' + }, 'update stream version wins on dedupe') + t.deepEquals(results[3], { + name: 'foo', + version: '1.0.0' + }, 'update stream version wins on dedupe even when the newer one has a lower semver.') + server.done() + cleanup() + t.end() + }) + }) +}) + +test('createEntryStream no sources', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + server.get('/-/all').once().reply(404, {}) + _createEntryStream(cachePath, ALL, {}, 600, function (err, stream, latest, newEntries) { + t.ok(err, 'no sources, got an error') + t.notOk(stream, 'no stream returned') + t.notOk(latest, 'no latest returned') + t.notOk(newEntries, 'no entries need to be written') + t.match(err.message, /No search sources available/, 'useful error message') + server.done() + cleanup() + t.end() + }) +}) + +test('cleanup', function (t) { + cleanup() + server.close() + t.pass('all done') + t.done() +}) diff --git a/deps/npm/test/tap/all-package-metadata-update-stream-unit.js b/deps/npm/test/tap/all-package-metadata-update-stream-unit.js new file mode 100644 index 0000000000..b9cf337eb9 --- /dev/null +++ b/deps/npm/test/tap/all-package-metadata-update-stream-unit.js @@ -0,0 +1,176 @@ +'use strict' + +var common = require('../common-tap.js') +var npm = require('../../') +var test = require('tap').test +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var path = require('path') +var mr = require('npm-registry-mock') +var ms = require('mississippi') + +var _createEntryUpdateStream = require('../../lib/search/all-package-metadata.js')._createEntryUpdateStream + +var ALL = common.registry + '/-/all' +var PKG_DIR = path.resolve(__dirname, 'create-entry-update-stream') +var CACHE_DIR = path.resolve(PKG_DIR, 'cache') + +var server + +function setup () { + mkdirp.sync(CACHE_DIR) +} + +function cleanup () { + rimraf.sync(PKG_DIR) +} + +test('setup', function (t) { + mr({port: common.port, throwOnUnmatched: true}, function (err, s) { + t.ifError(err, 'registry mocked successfully') + npm.load({ cache: CACHE_DIR, registry: common.registry }, function (err) { + t.ifError(err, 'npm loaded successfully') + server = s + t.pass('all set up') + t.done() + }) + }) +}) + +test('createEntryUpdateStream full request', function (t) { + setup() + server.get('/-/all').once().reply(200, { + '_updated': 1234, + 'bar': { name: 'bar', version: '1.0.0' }, + 'foo': { name: 'foo', version: '1.0.0' } + }, { + date: Date.now() // should never be used. + }) + _createEntryUpdateStream(ALL, {}, 600, 0, function (err, stream, latest) { + if (err) throw err + t.equals(latest, 1234, '`latest` correctly extracted') + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + if (err) throw err + t.deepEquals(results, [{ + name: 'bar', + version: '1.0.0' + }, { + name: 'foo', + version: '1.0.0' + }]) + server.done() + cleanup() + t.end() + }) + }) +}) + +test('createEntryUpdateStream partial update', function (t) { + setup() + var now = Date.now() + server.get('/-/all/since?stale=update_after&startkey=1234').once().reply(200, { + 'bar': { name: 'bar', version: '1.0.0' }, + 'foo': { name: 'foo', version: '1.0.0' } + }, { + date: (new Date(now)).toISOString() + }) + _createEntryUpdateStream(ALL, {}, 600, 1234, function (err, stream, latest) { + if (err) throw err + t.equals(latest, now, '`latest` correctly extracted from header') + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + if (err) throw err + t.deepEquals(results, [{ + name: 'bar', + version: '1.0.0' + }, { + name: 'foo', + version: '1.0.0' + }]) + server.done() + cleanup() + t.end() + }) + }) +}) + +test('createEntryUpdateStream authed request', function (t) { + setup() + var token = 'thisisanauthtoken' + server.get('/-/all', { authorization: 'Bearer ' + token }).once().reply(200, { + '_updated': 1234, + 'bar': { name: 'bar', version: '1.0.0' }, + 'foo': { name: 'foo', version: '1.0.0' } + }, { + date: Date.now() // should never be used. + }) + _createEntryUpdateStream(ALL, { token: token }, 600, 0, function (err, stream, latest) { + if (err) throw err + t.equals(latest, 1234, '`latest` correctly extracted') + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + if (err) throw err + t.deepEquals(results, [{ + name: 'bar', + version: '1.0.0' + }, { + name: 'foo', + version: '1.0.0' + }]) + server.done() + cleanup() + t.end() + }) + }) +}) + +test('createEntryUpdateStream bad auth', function (t) { + setup() + var token = 'thisisanauthtoken' + server.get('/-/all', { authorization: 'Bearer ' + token }).once().reply(401, { + error: 'unauthorized search request' + }) + _createEntryUpdateStream(ALL, { token: token }, 600, 0, function (err, stream, latest) { + t.ok(err, 'got an error from auth failure') + t.notOk(stream, 'no stream returned') + t.notOk(latest, 'no latest returned') + t.match(err, /unauthorized/, 'failure message from request used') + server.done() + cleanup() + t.end() + }) +}) + +test('createEntryUpdateStream not stale', function (t) { + setup() + var now = Date.now() + var staleness = 600 + _createEntryUpdateStream(ALL, {}, staleness, now, function (err, stream, latest) { + t.ifError(err, 'completed successfully') + t.notOk(stream, 'no stream returned') + t.notOk(latest, 'no latest returned') + server.done() + cleanup() + t.end() + }) +}) + +test('cleanup', function (t) { + cleanup() + server.close() + t.pass('all done') + t.done() +}) diff --git a/deps/npm/test/tap/all-package-metadata-write-stream-unit.js b/deps/npm/test/tap/all-package-metadata-write-stream-unit.js new file mode 100644 index 0000000000..410f7f9e9d --- /dev/null +++ b/deps/npm/test/tap/all-package-metadata-write-stream-unit.js @@ -0,0 +1,130 @@ +'use strict' + +var common = require('../common-tap.js') +var npm = require('../../') +var test = require('tap').test +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var path = require('path') +var fs = require('fs') +var ms = require('mississippi') + +var _createCacheWriteStream = require('../../lib/search/all-package-metadata.js')._createCacheWriteStream + +var PKG_DIR = path.resolve(__dirname, 'create-cache-write-stream') +var CACHE_DIR = path.resolve(PKG_DIR, 'cache') + +function setup () { + mkdirp.sync(CACHE_DIR) +} + +function cleanup () { + rimraf.sync(PKG_DIR) +} + +function fromArray (array) { + var idx = 0 + return ms.from.obj(function (size, next) { + next(null, array[idx++] || null) + }) +} + +test('setup', function (t) { + // This is pretty much only used for `getCacheStat` in the implementation + npm.load({ cache: CACHE_DIR, registry: common.registry }, function (err) { + t.ifError(err, 'npm successfully loaded') + t.done() + }) +}) + +test('createCacheEntryStream basic', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + var latest = 12345 + var src = [ + { name: 'bar', version: '1.0.0' }, + { name: 'foo', version: '1.0.0' } + ] + var srcStream = fromArray(src) + _createCacheWriteStream(cachePath, latest, function (err, stream) { + if (err) throw err + t.ok(stream, 'returned a stream') + stream = ms.pipeline.obj(srcStream, stream) + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + if (err) throw err + t.deepEquals(results, [{ + name: 'bar', + version: '1.0.0' + }, { + name: 'foo', + version: '1.0.0' + }]) + var fileData = JSON.parse(fs.readFileSync(cachePath)) + t.ok(fileData, 'cache contents written to the right file') + t.deepEquals(fileData, { + '_updated': latest, + bar: { + name: 'bar', + version: '1.0.0' + }, + foo: { + name: 'foo', + version: '1.0.0' + } + }, 'cache contents based on what was written') + cleanup() + t.done() + }) + }) +}) + +test('createCacheEntryStream no entries', function (t) { + cleanup() // wipe out the cache dir + var cachePath = path.join(CACHE_DIR, '.cache.json') + var latest = 12345 + var src = [] + var srcStream = fromArray(src) + _createCacheWriteStream(cachePath, latest, function (err, stream) { + if (err) throw err + t.ok(stream, 'returned a stream') + stream = ms.pipeline.obj(srcStream, stream) + stream.resume() + ms.finished(stream, function (err) { + if (err) throw err + var fileData = JSON.parse(fs.readFileSync(cachePath)) + t.ok(fileData, 'cache file exists and has stuff in it') + cleanup() + t.done() + }) + }) +}) + +test('createCacheEntryStream missing cache dir', function (t) { + setup() + var cachePath = path.join(CACHE_DIR, '.cache.json') + var latest = 12345 + var src = [] + var srcStream = fromArray(src) + _createCacheWriteStream(cachePath, latest, function (err, stream) { + if (err) throw err + t.ok(stream, 'returned a stream') + stream = ms.pipeline.obj(srcStream, stream) + stream.on('data', function (pkg) { + t.notOk(pkg, 'stream should not have output any data') + }) + ms.finished(stream, function (err) { + if (err) throw err + var fileData = JSON.parse(fs.readFileSync(cachePath)) + t.ok(fileData, 'cache contents written to the right file') + t.deepEquals(fileData, { + '_updated': latest + }, 'cache still contains `_updated`') + cleanup() + t.done() + }) + }) +}) diff --git a/deps/npm/test/tap/all-package-metadata.js b/deps/npm/test/tap/all-package-metadata.js new file mode 100644 index 0000000000..9b60822e4e --- /dev/null +++ b/deps/npm/test/tap/all-package-metadata.js @@ -0,0 +1,202 @@ +'use strict' + +var common = require('../common-tap.js') +var npm = require('../../') +var test = require('tap').test +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var path = require('path') +var fs = require('fs') +var cacheFile = require('npm-cache-filename') +var mr = require('npm-registry-mock') +var ms = require('mississippi') +var Tacks = require('tacks') +var File = Tacks.File + +var allPackageMetadata = require('../../lib/search/all-package-metadata.js') + +var PKG_DIR = path.resolve(__dirname, 'update-index') +var CACHE_DIR = path.resolve(PKG_DIR, 'cache') +var cacheBase +var cachePath + +var server + +function setup () { + mkdirp.sync(cacheBase) +} + +function cleanup () { + rimraf.sync(PKG_DIR) +} + +test('setup', function (t) { + mr({port: common.port, throwOnUnmatched: true}, function (err, s) { + t.ifError(err, 'registry mocked successfully') + npm.load({ cache: CACHE_DIR, registry: common.registry }, function (err) { + t.ifError(err, 'npm loaded successfully') + npm.config.set('cache', CACHE_DIR) + cacheBase = cacheFile(npm.config.get('cache'))(common.registry + '/-/all') + cachePath = path.join(cacheBase, '.cache.json') + server = s + t.pass('all set up') + t.done() + }) + }) +}) + +test('allPackageMetadata full request', function (t) { + setup() + var updated = Date.now() + server.get('/-/all').once().reply(200, { + '_updated': 1234, + 'bar': { name: 'bar', version: '1.0.0' }, + 'foo': { name: 'foo', version: '1.0.0' } + }, { + date: updated + }) + var stream = allPackageMetadata(600) + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + if (err) throw err + t.deepEquals(results, [{ + name: 'bar', + version: '1.0.0' + }, { + name: 'foo', + version: '1.0.0' + }]) + var fileData = JSON.parse(fs.readFileSync(cachePath)) + t.ok(fileData, 'cache contents written to the right file') + t.deepEquals(fileData, { + '_updated': 1234, + bar: { + name: 'bar', + version: '1.0.0' + }, + foo: { + name: 'foo', + version: '1.0.0' + } + }, 'cache contents based on what was written') + server.done() + cleanup() + t.end() + }) +}) + +test('allPackageMetadata cache only', function (t) { + setup() + var now = Date.now() + var cacheContents = { + '_updated': now, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + } + var fixture = new Tacks(File(cacheContents)) + fixture.create(cachePath) + var stream = allPackageMetadata(10000000) + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + t.ifError(err, 'stream finished without error') + t.deepEquals( + results.map(function (pkg) { return pkg.name }), + ['bar', 'cool', 'foo', 'other'], + 'packages deduped and sorted, without _updated' + ) + var fileData = JSON.parse(fs.readFileSync(cachePath)) + t.ok(fileData, 'cache contents written to the right file') + t.deepEquals(fileData, cacheContents, 'cacheContents written directly') + server.done() + cleanup() + t.end() + }) +}) + +test('createEntryStream merged stream', function (t) { + setup() + var now = Date.now() + var cacheTime = now - 601000 + var reqTime = (new Date(now)).toISOString() + server.get('/-/all/since?stale=update_after&startkey=' + cacheTime).once().reply(200, { + 'bar': { name: 'bar', version: '2.0.0' }, + 'car': { name: 'car', version: '1.0.0' }, + 'foo': { name: 'foo', version: '1.0.0' } + }, { + date: reqTime + }) + var fixture = new Tacks(File({ + '_updated': cacheTime, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + })) + fixture.create(cachePath) + var stream = allPackageMetadata(600) + t.ok(stream, 'returned a stream') + var results = [] + stream.on('data', function (pkg) { + results.push(pkg) + }) + ms.finished(stream, function (err) { + t.ifError(err, 'stream finished without error') + t.deepEquals( + results.map(function (pkg) { return pkg.name }), + ['bar', 'car', 'cool', 'foo', 'other'], + 'packages deduped and sorted' + ) + t.deepEquals(results[0], { + name: 'bar', + version: '2.0.0' + }, 'update stream version wins on dedupe') + t.deepEquals(results[3], { + name: 'foo', + version: '1.0.0' + }, 'update stream version wins on dedupe even when the newer one has a lower semver.') + var cacheContents = { + '_updated': Date.parse(reqTime), + bar: { name: 'bar', version: '2.0.0' }, + car: { name: 'car', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '1.0.0' }, + other: { name: 'other', version: '1.0.0' } + } + var fileData = JSON.parse(fs.readFileSync(cachePath)) + t.ok(fileData, 'cache contents written to the right file') + t.deepEquals(fileData, cacheContents, 'cache updated correctly') + server.done() + cleanup() + t.end() + }) +}) + +test('allPackageMetadata no sources', function (t) { + setup() + server.get('/-/all').once().reply(404, {}) + var stream = allPackageMetadata(600) + ms.finished(stream, function (err) { + t.ok(err, 'no sources, got an error') + t.match(err.message, /No search sources available/, 'useful error message') + server.done() + cleanup() + t.end() + }) +}) + +test('cleanup', function (t) { + cleanup() + server.close() + t.pass('all done') + t.done() +}) diff --git a/deps/npm/test/network/bearer-token-check.js b/deps/npm/test/tap/bearer-token-check.js index 8ddbec29a4..f400ec1726 100644 --- a/deps/npm/test/network/bearer-token-check.js +++ b/deps/npm/test/tap/bearer-token-check.js @@ -15,7 +15,7 @@ var outfile = resolve(pkg, '_npmrc') var modules = resolve(pkg, 'node_modules') var tarballPath = '/scoped-underscore/-/scoped-underscore-1.3.1.tgz' // needs to be a different hostname to verify tokens (not) being sent correctly -var tarballURL = 'http://lvh.me:' + common.port + tarballPath +var tarballURL = 'http://127.0.0.1:' + common.port + tarballPath var tarball = resolve(__dirname, '../fixtures/scoped-underscore-1.3.1.tgz') var server @@ -47,17 +47,19 @@ test('authed npm install with tarball not on registry', function (t) { '--loglevel', 'silent', '--json', '--fetch-retries', 0, + '--registry', common.registry, '--userconfig', outfile ], EXEC_OPTS, function (err, code, stdout, stderr) { t.ifError(err, 'test runner executed without error') t.equal(code, 0, 'npm install exited OK') + t.comment(stdout.trim()) + t.comment(stderr.trim()) t.notOk(stderr, 'no output on stderr') try { var results = JSON.parse(stdout) } catch (ex) { - console.error('#', ex) t.ifError(ex, 'stdout was valid JSON') } @@ -65,7 +67,7 @@ test('authed npm install with tarball not on registry', function (t) { var installedversion = { 'version': '1.3.1', 'from': '>=1.3.1 <2', - 'resolved': 'http://lvh.me:1337/scoped-underscore/-/scoped-underscore-1.3.1.tgz' + 'resolved': tarballURL } t.isDeeply(results.dependencies['@scoped/underscore'], installedversion, '@scoped/underscore installed') } diff --git a/deps/npm/test/tap/bundled-no-add-to-move.js b/deps/npm/test/tap/bundled-no-add-to-move.js new file mode 100644 index 0000000000..526af0e431 --- /dev/null +++ b/deps/npm/test/tap/bundled-no-add-to-move.js @@ -0,0 +1,46 @@ +'use strict' +var test = require('tap').test +var Node = require('../../lib/install/node.js').create +var diffTrees = require('../../lib/install/diff-trees.js')._diffTrees +var sortActions = require('../../lib/install/diff-trees.js').sortActions + +var oldTree = Node({ + path: '/', + location: '/', + children: [ + Node({ + package: {name: 'one', version: '1.0.0'}, + path: '/node_modules/one', + location: '/one' + }) + ] +}) +oldTree.children[0].requiredBy.push(oldTree) + +var newTree = Node({ + path: '/', + location: '/', + children: [ + Node({ + package: {name: 'abc', version: '1.0.0'}, + path: '/node_modules/abc', + location: '/abc', + children: [ + Node({ + package: {name: 'one', version: '1.0.0'}, + fromBundle: true, + path: '/node_modules/abc/node_modules/one', + location: '/abc/one' + }) + ] + }) + ] +}) +newTree.children[0].requiredBy.push(newTree) +newTree.children[0].children[0].requiredBy.push(newTree.children[0]) + +test('test', function (t) { + var differences = sortActions(diffTrees(oldTree, newTree)).map(function (diff) { return diff[0] + diff[1].location }) + t.isDeeply(differences, ['add/abc/one', 'remove/one', 'add/abc'], 'bundled add/remove stays add/remove') + t.end() +}) diff --git a/deps/npm/test/tap/ci-header.js b/deps/npm/test/tap/ci-header.js new file mode 100644 index 0000000000..dc20cc53c3 --- /dev/null +++ b/deps/npm/test/tap/ci-header.js @@ -0,0 +1,139 @@ +'use strict' +var path = require('path') +var test = require('tap').test +var mr = require('npm-registry-mock') +var Tacks = require('tacks') +var File = Tacks.File +var Dir = Tacks.Dir +var chain = require('slide').chain +var common = require('../common-tap.js') + +var basedir = path.join(__dirname, path.basename(__filename, '.js')) +var testdir = path.join(basedir, 'testdir') +var cachedir = path.join(basedir, 'cache') +var globaldir = path.join(basedir, 'global') +var tmpdir = path.join(basedir, 'tmp') + +var ciKeys = ['CI', 'TDDIUM', 'JENKINS_URL', 'bamboo.buildKey'] + +var filteredEnv = common.newEnv().delete(ciKeys) + +var conf = function (extraEnv) { + return { + cwd: testdir, + env: filteredEnv.clone().extend(extraEnv, { + npm_config_cache: cachedir, + npm_config_tmp: tmpdir, + npm_config_prefix: globaldir, + npm_config_registry: common.registry, + npm_config_loglevel: 'warn' + }) + } +} + +var server +var fixture = new Tacks(Dir({ + cache: Dir(), + global: Dir(), + tmp: Dir(), + testdir: Dir({ + 'package.json': File({ + name: 'example', + version: '1.0.0' + }) + }) +})) + +function setup () { + cleanup() + fixture.create(basedir) +} + +function cleanup () { + fixture.remove(basedir) +} + +var ciHeaderTestCI = { + 'time': {'1.0.0': ''}, + 'dist-tags': {'latest': '1.0.0'}, + 'versions': { + '1.0.0': { + name: 'ci-header-test', + version: '1.0.0', + gotCI: true + } + } +} +var ciHeaderTestNotCI = { + 'time': {'1.0.0': ''}, + 'dist-tags': {'latest': '1.0.0'}, + 'versions': { + '1.0.0': { + name: 'ci-header-test', + version: '1.0.0', + gotCI: false + } + } +} + +var ciHeaderTestNoCI = { + 'time': {'1.0.0': ''}, + 'dist-tags': {'latest': '1.0.0'}, + 'versions': { + '1.0.0': { + name: 'ci-header-test', + version: '1.0.0', + gotCI: null + } + } +} + +test('setup', function (t) { + setup() + mr({port: common.port, throwOnUnmatched: true}, function (err, s) { + if (err) throw err + server = s + server.get('/ci-header-test', { + 'NPM-In-CI': 'true' + }).many().reply('200', ciHeaderTestCI) + server.get('/ci-header-test', { + 'NPM-In-CI': 'false' + }).many().reply('200', ciHeaderTestNotCI) + server.get('/ci-header-test', {}).many().reply('200', ciHeaderTestNoCI) + t.done() + }) +}) + +test('various-ci', function (t) { + var todo = ciKeys.map(function (key) { return [checkKey, key] }) + return chain(todo, t.done) + + function checkKey (key, next) { + var env = {} + env[key] = 'true' + + common.npm(['view', '--cache-min=0', 'ci-header-test', 'gotCI'], conf(env), function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, key + ' command ran ok') + if (stderr.trim()) t.comment(stderr.trim()) + t.is(stdout.trim(), 'true', key + ' set results in CI header') + next() + }) + } +}) + +test('no-ci', function (t) { + common.npm(['view', '--cache-min=0', 'ci-header-test', 'gotCI'], conf(), function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'No CI env, command ran ok') + if (stderr.trim()) t.comment(stderr.trim()) + t.is(stdout.trim(), 'false', 'No CI env, not in CI') + t.done() + }) +}) + +test('cleanup', function (t) { + server.close() + cleanup() + t.done() +}) diff --git a/deps/npm/test/tap/config-meta.js b/deps/npm/test/tap/config-meta.js index bb40e2038d..3cd1bfa7da 100644 --- a/deps/npm/test/tap/config-meta.js +++ b/deps/npm/test/tap/config-meta.js @@ -67,7 +67,7 @@ test('get lines', function (t) { line: i } } - } else if (exceptions.indexOf(f) === -1) { + } else if (exceptions.indexOf(f) === -1 && f.indexOf('.cache') === -1) { t.fail('non-string-literal config used in ' + f + ':' + i) } }) diff --git a/deps/npm/test/tap/install-actions.js b/deps/npm/test/tap/install-actions.js index ad75cacb5e..824a31c1be 100644 --- a/deps/npm/test/tap/install-actions.js +++ b/deps/npm/test/tap/install-actions.js @@ -56,7 +56,7 @@ test('->optdep:a->dep:b', function (t) { moduleB.parent = tree t.plan(3) - actions.postinstall('/', '/', moduleA, mockLog, function (er) { + actions.postinstall('/', moduleA, mockLog, function (er) { t.is(er && er.code, 'ELIFECYCLE', 'Lifecycle failed') t.ok(moduleA.failed, 'moduleA (optional dep) is marked failed') t.ok(moduleB.failed, 'moduleB (direct dep of moduleA) is marked as failed') @@ -108,7 +108,7 @@ test('->dep:b,->optdep:a->dep:b', function (t) { moduleB.parent = tree t.plan(3) - actions.postinstall('/', '/', moduleA, mockLog, function (er) { + actions.postinstall('/', moduleA, mockLog, function (er) { t.ok(er && er.code === 'ELIFECYCLE', 'Lifecycle failed') t.ok(moduleA.failed, 'moduleA (optional dep) is marked failed') t.ok(!moduleB.failed, 'moduleB (direct dep of moduleA) is marked as failed') diff --git a/deps/npm/test/tap/install-scoped-with-bundled-dependency.js b/deps/npm/test/tap/install-scoped-with-bundled-dependency.js new file mode 100644 index 0000000000..7a620dfdcd --- /dev/null +++ b/deps/npm/test/tap/install-scoped-with-bundled-dependency.js @@ -0,0 +1,84 @@ +'use strict' +var path = require('path') +var test = require('tap').test +var Tacks = require('tacks') +var File = Tacks.File +var Dir = Tacks.Dir +var extend = Object.assign || require('util')._extend +var common = require('../common-tap.js') + +var basedir = path.join(__dirname, path.basename(__filename, '.js')) +var testdir = path.join(basedir, 'testdir') +var cachedir = path.join(basedir, 'cache') +var globaldir = path.join(basedir, 'global') +var tmpdir = path.join(basedir, 'tmp') + +var conf = { + cwd: testdir, + env: extend({ + npm_config_cache: cachedir, + npm_config_tmp: tmpdir, + npm_config_prefix: globaldir, + npm_config_registry: common.registry, + npm_config_loglevel: 'warn' + }, process.env) +} + +var fixture = new Tacks(Dir({ + cache: Dir(), + global: Dir(), + tmp: Dir(), + testdir: Dir({ + node_modules: Dir({ + }), + package: Dir({ + node_modules: Dir({ + 'bundled-dep': Dir({ + 'package.json': File({ + name: 'bundled-dep', + version: '0.0.0' + }) + }) + }), + 'package.json': File({ + name: '@scope/package', + version: '0.0.0', + dependencies: { + 'bundled-dep': '*' + }, + bundledDependencies: [ + 'bundled-dep' + ] + }) + }) + }) +})) + +function setup () { + cleanup() + fixture.create(basedir) +} + +function cleanup () { + fixture.remove(basedir) +} + +test('setup', function (t) { + setup() + t.done() +}) + +test('install dependencies of bundled dependencies', function (t) { + common.npm(['install', './package'], conf, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stdout.trim()) + t.comment(stderr.trim()) + t.done() + }) +}) + +test('cleanup', function (t) { + cleanup() + t.done() +}) diff --git a/deps/npm/test/network/it.js b/deps/npm/test/tap/it.js index 6fc2a6fd49..0381289709 100644 --- a/deps/npm/test/network/it.js +++ b/deps/npm/test/tap/it.js @@ -35,7 +35,7 @@ test('run up the mock registry', function (t) { test('npm install-test', function (t) { setup() - common.npm('install-test', { cwd: pkg }, function (err, code, stdout, stderr) { + common.npm(['install-test', '--registry=' + common.registry], { cwd: pkg }, function (err, code, stdout, stderr) { if (err) throw err t.equal(code, 0, 'command ran without error') t.ok(statSync(installed), 'package was installed') @@ -48,7 +48,7 @@ test('npm install-test', function (t) { test('npm it (the form most people will use)', function (t) { setup() - common.npm('it', { cwd: pkg }, function (err, code, stdout, stderr) { + common.npm(['it', '--registry=' + common.registry], { cwd: pkg }, function (err, code, stdout, stderr) { if (err) throw err t.equal(code, 0, 'command ran without error') t.ok(statSync(installed), 'package was installed') diff --git a/deps/npm/test/slow/legacy-npm-self-install.js b/deps/npm/test/tap/legacy-npm-self-install.js index 57880496c7..2051802c2e 100644 --- a/deps/npm/test/slow/legacy-npm-self-install.js +++ b/deps/npm/test/tap/legacy-npm-self-install.js @@ -45,7 +45,6 @@ test('npm-self-install', function (t) { var pathsep = isWin32 ? ';' : ':' env.npm_config_prefix = globalpath env.npm_config_global = 'true' - env.npm_config_npat = 'false' env.NODE_PATH = null env.npm_config_user_agent = null env.npm_config_color = 'always' diff --git a/deps/npm/test/network/legacy-optional-deps.js b/deps/npm/test/tap/legacy-optional-deps.js index 80ad7cc47e..08ac98522d 100644 --- a/deps/npm/test/network/legacy-optional-deps.js +++ b/deps/npm/test/tap/legacy-optional-deps.js @@ -22,7 +22,7 @@ var fixture = new Tacks( name: 'npm-test-optional-deps', version: '1.2.5', optionalDependencies: { - 'npm-test-foobarzaaakakaka': 'http://example.com/', + 'npm-test-foobarzaaakakaka': common.registry + '/not-a-package', async: '10.999.14234', mkdirp: '0.3.5', optimist: 'some invalid version 99 #! $$ x y z', @@ -45,6 +45,7 @@ test('setup', function (t) { test('optional-deps', function (t) { server.get('/npm-test-failer').reply(404, {error: 'nope'}) + server.get('/not-a-package').reply(200, 'HI THERE') var opts = ['--registry=' + common.registry, '--timeout=100'] common.npm(opts.concat(['install', fixturepath]), {cwd: basepath}, installCheckAndTest) diff --git a/deps/npm/test/tap/move-no-clobber-dest-node-modules.js b/deps/npm/test/tap/move-no-clobber-dest-node-modules.js new file mode 100644 index 0000000000..4e487837ad --- /dev/null +++ b/deps/npm/test/tap/move-no-clobber-dest-node-modules.js @@ -0,0 +1,139 @@ +'use strict' +var path = require('path') +var test = require('tap').test +var fs = require('graceful-fs') +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var base = path.join(__dirname, path.basename(__filename, '.js')) +var common = require('../common-tap.js') + +function fixturepath () { + return path.join(base, path.join.apply(path, arguments)) +} + +function File (contents) { + return { + type: 'file', + contents: typeof contents === 'object' + ? JSON.stringify(contents) + : contents + } +} + +function Dir (contents) { + return { + type: 'dir', + contents: contents || {} + } +} + +function createFixtures (filename, fixture) { + if (fixture.type === 'dir') { + mkdirp.sync(filename) + Object.keys(fixture.contents).forEach(function (content) { + createFixtures(path.resolve(filename, content), fixture.contents[content]) + }) + } else if (fixture.type === 'file') { + fs.writeFileSync(filename, fixture.contents) + } else { + throw new Error('Unknown fixture type: ' + fixture.type) + } +} + +var fixtures = Dir({ +// The fixture modules + 'moda@1.0.1': Dir({ + 'package.json': File({ + name: 'moda', + version: '1.0.1' + }) + }), + 'modb@1.0.0': Dir({ + 'package.json': File({ + name: 'modb', + version: '1.0.0', + bundleDependencies: ['modc'], + dependencies: { + modc: fixturepath('modc@1.0.0') + } + }) + }), + 'modc@1.0.0': Dir({ + 'package.json': File({ + name: 'modc', + version: '1.0.0' + }) + }), +// The local config + 'package.json': File({ + dependencies: { + moda: fixturepath('moda@1.0.1'), + modb: fixturepath('modb@1.0.0') + } + }), + 'node_modules': Dir({ + 'moda': Dir({ + 'package.json': File({ + name: 'moda', + version: '1.0.0', + _requested: { rawSpec: fixturepath('moda@1.0.0') }, + dependencies: { + modb: fixturepath('modb@1.0.0') + }, + bundleDependencies: [ 'modb' ] + }) + }) + }) +}) + +function setup () { + cleanup() + createFixtures(base, fixtures) +} + +function cleanup () { + rimraf.sync(base) +} + +function exists (t, filepath, msg) { + try { + fs.statSync(filepath) + t.pass(msg) + return true + } catch (ex) { + t.fail(msg, {found: null, wanted: 'exists', compare: 'fs.stat(' + filepath + ')'}) + return false + } +} + +test('setup', function (t) { + setup() + common.npm('install', {cwd: fixturepath('modb@1.0.0')}, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'modb') + common.npm('install', { + cwd: fixturepath('node_modules', 'moda') + }, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'installed moda') + t.done() + }) + }) +}) + +test('no-clobber', function (t) { + common.npm('install', {cwd: base}, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'installed ok') + exists(t, fixturepath('node_modules', 'moda'), 'moda') + exists(t, fixturepath('node_modules', 'modb'), 'modb') + exists(t, fixturepath('node_modules', 'modb', 'node_modules', 'modc'), 'modc') + t.done() + }) +}) + +test('cleanup', function (t) { + cleanup() + t.pass() + t.done() +}) diff --git a/deps/npm/test/tap/outdated-color.js b/deps/npm/test/tap/outdated-color.js index a0da337026..7fc8c521e5 100644 --- a/deps/npm/test/tap/outdated-color.js +++ b/deps/npm/test/tap/outdated-color.js @@ -57,7 +57,7 @@ test('does not use ansi styling', function (t) { EXEC_OPTS, function (err, code, stdout) { t.ifError(err) - t.notOk(code, 'npm outdated exited with code 0') + t.is(code, 1, 'npm outdated exited with code 1') t.ok(stdout, stdout.length) t.ok(!hasControlCodes(stdout)) s.close() diff --git a/deps/npm/test/tap/outdated-depth-deep.js b/deps/npm/test/tap/outdated-depth-deep.js index 682b1ca958..14d4f1faf8 100644 --- a/deps/npm/test/tap/outdated-depth-deep.js +++ b/deps/npm/test/tap/outdated-depth-deep.js @@ -71,8 +71,8 @@ test('outdated depth deep (9999)', function (t) { function thenValidateOutput (err, code, stdout, stderr) { if (err) throw err - t.is(code, 0, 'outdated completed successfully') - t.is('', stderr, 'no error output') + t.ifError(err) + t.is(code, 1, 'npm outdated exited with code 1') t.match( stdout, /underscore.*1\.3\.1.*1\.3\.1.*1\.5\.1.*whatever\n/g, diff --git a/deps/npm/test/tap/outdated-depth-integer.js b/deps/npm/test/tap/outdated-depth-integer.js index 32bf785f27..5e743a0a77 100644 --- a/deps/npm/test/tap/outdated-depth-integer.js +++ b/deps/npm/test/tap/outdated-depth-integer.js @@ -62,7 +62,9 @@ test('outdated depth integer', function (t) { npm.install('request@0.9.0', function (er) { if (er) throw new Error(er) npm.outdated(function (err, d) { - if (err) throw new Error(err) + t.ifError(err, 'outdated did not error') + t.is(process.exitCode, 1, 'exitCode set to 1') + process.exitCode = 0 t.deepEqual(d, expected) s.close() t.end() diff --git a/deps/npm/test/tap/outdated-depth.js b/deps/npm/test/tap/outdated-depth.js index 13220dfa7b..91523405e8 100644 --- a/deps/npm/test/tap/outdated-depth.js +++ b/deps/npm/test/tap/outdated-depth.js @@ -54,7 +54,9 @@ test('outdated depth zero', function (t) { npm.install('.', function (er) { if (er) throw new Error(er) npm.outdated(function (err, d) { - if (err) throw new Error(err) + t.ifError(err, 'npm outdated ran without error') + t.is(process.exitCode, 1, 'exit code set to 1') + process.exitCode = 0 t.deepEqual(d[0], expected) s.close() t.end() diff --git a/deps/npm/test/tap/outdated-git.js b/deps/npm/test/tap/outdated-git.js index 1a61a0c4d2..2a595e5288 100644 --- a/deps/npm/test/tap/outdated-git.js +++ b/deps/npm/test/tap/outdated-git.js @@ -31,9 +31,12 @@ test('setup', function (t) { test('discovers new versions in outdated', function (t) { process.chdir(pkg) - t.plan(5) + t.plan(7) npm.load({cache: cache, registry: common.registry, loglevel: 'silent'}, function () { npm.commands.outdated([], function (er, d) { + t.ifError(er, 'npm outdated completed successfully') + t.is(process.exitCode, 1, 'exitCode set to 1') + process.exitCode = 0 t.equal(d[0][3], 'git') t.equal(d[0][4], 'git') t.equal(d[0][5], 'github:robertkowalski/foo') diff --git a/deps/npm/test/tap/outdated-include-devdependencies.js b/deps/npm/test/tap/outdated-include-devdependencies.js index 7ab0088430..331d0b3b33 100644 --- a/deps/npm/test/tap/outdated-include-devdependencies.js +++ b/deps/npm/test/tap/outdated-include-devdependencies.js @@ -37,6 +37,9 @@ test('includes devDependencies in outdated', function (t) { mr({ port: common.port }, function (er, s) { npm.load({ cache: cache, registry: common.registry }, function () { npm.outdated(function (er, d) { + t.ifError(er, 'npm outdated completed successfully') + t.is(process.exitCode, 1, 'exitCode set to 1') + process.exitCode = 0 t.equal('1.5.1', d[0][3]) s.close() t.end() diff --git a/deps/npm/test/tap/outdated-json.js b/deps/npm/test/tap/outdated-json.js index 2dd6867e32..39d54fb196 100644 --- a/deps/npm/test/tap/outdated-json.js +++ b/deps/npm/test/tap/outdated-json.js @@ -79,8 +79,8 @@ test('it should log json data', function (t) { ], EXEC_OPTS, function (err, code, stdout) { - t.ifError(err, 'npm outdated ran without issue') - t.notOk(code, 'npm outdated ran without raising error code') + t.ifError(err, 'npm outdated ran without error') + t.is(code, 1, 'npm outdated exited with code 1') var out t.doesNotThrow(function () { out = JSON.parse(stdout) diff --git a/deps/npm/test/tap/outdated-local.js b/deps/npm/test/tap/outdated-local.js index 067696ca90..c253331d07 100644 --- a/deps/npm/test/tap/outdated-local.js +++ b/deps/npm/test/tap/outdated-local.js @@ -78,7 +78,7 @@ test('setup', function (t) { }) test('outdated support local modules', function (t) { - t.plan(4) + t.plan(5) process.chdir(pkg) mr({ port: common.port, plugin: mocks }, function (err, s) { t.ifError(err, 'mock registry started without problems') @@ -115,7 +115,9 @@ test('outdated support local modules', function (t) { t.ifError(err, 'install success') bumpLocalModules() npm.outdated(function (er, d) { - t.ifError(er, 'outdated success') + t.ifError(err, 'npm outdated ran without error') + t.is(process.exitCode, 1, 'errorCode set to 1') + process.exitCode = 0 t.ok(verify(d, [ [ path.resolve(__dirname, 'outdated-local'), diff --git a/deps/npm/test/tap/outdated-long.js b/deps/npm/test/tap/outdated-long.js index 87e1f23b21..8fdf3057bf 100644 --- a/deps/npm/test/tap/outdated-long.js +++ b/deps/npm/test/tap/outdated-long.js @@ -75,8 +75,9 @@ test('it should not throw', function (t) { t.ifError(err, 'install success') npm.config.set('long', true) npm.outdated(function (er, d) { - t.ifError(er, 'outdated success') - + t.ifError(err, 'npm outdated ran without error') + t.is(process.exitCode, 1, 'exit code set to 1') + process.exitCode = 0 console.log = originalLog t.same(output, expOut) diff --git a/deps/npm/test/tap/outdated-new-versions.js b/deps/npm/test/tap/outdated-new-versions.js index c6fbd426dc..bfd63e18c3 100644 --- a/deps/npm/test/tap/outdated-new-versions.js +++ b/deps/npm/test/tap/outdated-new-versions.js @@ -36,11 +36,14 @@ test('setup', function (t) { test('dicovers new versions in outdated', function (t) { process.chdir(pkg) - t.plan(2) + t.plan(4) mr({ port: common.port }, function (er, s) { npm.load({ cache: cache, registry: common.registry }, function () { npm.outdated(function (er, d) { + t.ifError(er, 'npm outdated completed successfully') + t.is(process.exitCode, 1, 'exitCode set to 1') + process.exitCode = 0 for (var i = 0; i < d.length; i++) { if (d[i][1] === 'underscore') t.equal('1.5.1', d[i][4]) if (d[i][1] === 'request') t.equal('2.27.0', d[i][4]) diff --git a/deps/npm/test/tap/outdated-private.js b/deps/npm/test/tap/outdated-private.js index 1d5e460c6e..39bd6f1ac9 100644 --- a/deps/npm/test/tap/outdated-private.js +++ b/deps/npm/test/tap/outdated-private.js @@ -52,7 +52,7 @@ test('setup', function (t) { }) test('outdated ignores private modules', function (t) { - t.plan(3) + t.plan(4) process.chdir(pkg) mr({ port: common.port }, function (er, s) { npm.load( @@ -66,7 +66,9 @@ test('outdated ignores private modules', function (t) { t.ifError(err, 'install success') bumpLocalPrivate() npm.outdated(function (er, d) { - t.ifError(er, 'outdated success') + t.ifError(err, 'npm outdated ran without error') + t.is(process.exitCode, 1, 'exitCode set to 1') + process.exitCode = 0 t.deepEqual(d, [[ path.resolve(__dirname, 'outdated-private'), 'underscore', diff --git a/deps/npm/test/network/outdated-symlink.js b/deps/npm/test/tap/outdated-symlink.js index 809cfec2e2..a7792f4a31 100644 --- a/deps/npm/test/network/outdated-symlink.js +++ b/deps/npm/test/tap/outdated-symlink.js @@ -15,7 +15,8 @@ var extend = Object.assign || require('util')._extend var fakeRoot = path.join(pkg, 'fakeRoot') var OPTS = { env: extend(extend({}, process.env), { - 'npm_config_prefix': fakeRoot + 'npm_config_prefix': fakeRoot, + 'registry': common.registry }) } diff --git a/deps/npm/test/tap/outdated.js b/deps/npm/test/tap/outdated.js index 3a46705e38..8b1907d95f 100644 --- a/deps/npm/test/tap/outdated.js +++ b/deps/npm/test/tap/outdated.js @@ -103,7 +103,9 @@ test('it should not throw', function (t) { output.push.apply(output, arguments) } npm.outdated(function (er, d) { - t.ifError(er, 'outdated success') + t.ifError(err, 'outdated completed without error') + t.equals(process.exitCode, 1, 'exitCode set to 1') + process.exitCode = 0 output = output.map(function (x) { return x.replace(/\r/g, '') }) console.log = originalLog diff --git a/deps/npm/test/tap/prepare.js b/deps/npm/test/tap/prepare.js new file mode 100644 index 0000000000..f179c52672 --- /dev/null +++ b/deps/npm/test/tap/prepare.js @@ -0,0 +1,85 @@ +// verify that prepare runs on pack and publish +var common = require('../common-tap') +var test = require('tap').test +var fs = require('graceful-fs') +var join = require('path').join +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') + +var pkg = join(__dirname, 'prepare_package') +var tmp = join(pkg, 'tmp') +var cache = join(pkg, 'cache') + +test('setup', function (t) { + var n = 0 + cleanup() + mkdirp(pkg, then()) + mkdirp(cache, then()) + mkdirp(tmp, then()) + function then () { + n++ + return function (er) { + if (er) throw er + if (--n === 0) next() + } + } + + function next () { + fs.writeFile(join(pkg, 'package.json'), JSON.stringify({ + name: 'npm-test-prepare', + version: '1.2.5', + scripts: { prepare: 'echo ok' } + }), 'ascii', function (er) { + if (er) throw er + + t.pass('setup done') + t.end() + }) + } +}) + +test('test', function (t) { + var env = { + 'npm_config_cache': cache, + 'npm_config_tmp': tmp, + 'npm_config_prefix': pkg, + 'npm_config_global': 'false' + } + + for (var i in process.env) { + if (!/^npm_config_/.test(i)) { + env[i] = process.env[i] + } + } + + common.npm([ + 'pack', + '--loglevel', 'warn' + ], { cwd: pkg, env: env }, function (err, code, stdout, stderr) { + t.equal(code, 0, 'pack finished successfully') + t.ifErr(err, 'pack finished successfully') + + t.notOk(stderr, 'got stderr data:' + JSON.stringify('' + stderr)) + var c = stdout.trim() + var regex = new RegExp( + '> npm-test-prepare@1.2.5 prepare [^\\r\\n]+\\r?\\n' + + '> echo ok\\r?\\n' + + '\\r?\\n' + + 'ok\\r?\\n' + + 'npm-test-prepare-1.2.5.tgz', 'ig' + ) + + t.match(c, regex) + t.end() + }) +}) + +test('cleanup', function (t) { + cleanup() + t.pass('cleaned up') + t.end() +}) + +function cleanup () { + rimraf.sync(pkg) +} diff --git a/deps/npm/test/tap/prepublish-only.js b/deps/npm/test/tap/prepublish-only.js new file mode 100644 index 0000000000..3681c76897 --- /dev/null +++ b/deps/npm/test/tap/prepublish-only.js @@ -0,0 +1,141 @@ +// verify that prepublishOnly runs _only_ on publish +var join = require('path').join + +var mr = require('npm-registry-mock') +var test = require('tap').test +var Tacks = require('tacks') +var File = Tacks.File +var Dir = Tacks.Dir + +var common = require('../common-tap') + +var pkg = join(__dirname, 'prepublish_package') +var cachedir = join(pkg, 'cache') +var tmpdir = join(pkg, 'tmp') + +var env = { + 'npm_config_cache': cachedir, + 'npm_config_tmp': tmpdir, + 'npm_config_prefix': pkg, + 'npm_config_global': 'false' +} + +for (var i in process.env) { + if (!/^npm_config_/.test(i)) { + env[i] = process.env[i] + } +} + +var server + +var fixture = new Tacks(Dir({ + cache: Dir(), + tmp: Dir(), + '.npmrc': File([ + 'progress=false', + 'registry=' + common.registry, + '//localhost:1337/:username=username', + '//localhost:1337/:_authToken=deadbeeffeed' + ].join('\n') + '\n'), + helper: Dir({ + 'script.js': File([ + '#!/usr/bin/env node\n', + 'console.log("ok")\n' + ].join('\n') + '\n' + ), + 'package.json': File({ + name: 'helper', + version: '6.6.6', + bin: './script.js' + }) + }), + 'package.json': File({ + name: 'npm-test-prepublish-only', + version: '1.2.5', + dependencies: { + 'helper': 'file:./helper' + }, + scripts: { + build: 'helper', + prepublishOnly: 'npm run build' + } + }) +})) + +test('setup', function (t) { + cleanup() + fixture.create(pkg) + mr({port: common.port, throwOnUnmatched: true}, function (err, s) { + t.ifError(err, 'registry mocked successfully') + server = s + common.npm( + [ + 'install', + '--loglevel', 'error', + '--cache', cachedir, + '--tmp', tmpdir + ], + { + cwd: pkg, + env: env + }, + function (err, code, stdout, stderr) { + t.equal(code, 0, 'install exited OK') + t.ifErr(err, 'installed successfully') + + t.notOk(stderr, 'got stderr data:' + JSON.stringify('' + stderr)) + + t.end() + } + ) + }) +}) + +test('test', function (t) { + server.filteringRequestBody(function () { return true }) + .put('/npm-test-prepublish-only', true) + .reply(201, {ok: true}) + + common.npm( + [ + 'publish', + '--loglevel', 'warn' + ], + { + cwd: pkg, + env: env + }, + function (err, code, stdout, stderr) { + t.equal(code, 0, 'publish ran without error') + t.ifErr(err, 'published successfully') + + t.notOk(stderr, 'got stderr data:' + JSON.stringify('' + stderr)) + var c = stdout.trim() + var regex = new RegExp( + '> npm-test-prepublish-only@1.2.5 prepublishOnly [^\\r\\n]+\\r?\\n' + + '> npm run build\\r?\\n' + + '\\r?\\n' + + '\\r?\\n' + + '> npm-test-prepublish-only@1.2.5 build [^\\r\\n]+\\r?\\n' + + '> helper\\r?\\n' + + '\\r?\\n' + + 'ok\\r?\\n' + + '\\+ npm-test-prepublish-only@1.2.5', 'ig' + ) + + t.match(c, regex) + t.end() + } + ) +}) + +test('cleanup', function (t) { + cleanup() + server.close() + t.pass('cleaned up') + t.end() +}) + +function cleanup () { + fixture.remove(pkg) +} diff --git a/deps/npm/test/tap/prepublish.js b/deps/npm/test/tap/prepublish.js index 556d01ebb0..ef55d79b3b 100644 --- a/deps/npm/test/tap/prepublish.js +++ b/deps/npm/test/tap/prepublish.js @@ -1,4 +1,4 @@ -// verify that prepublish runs on pack and publish +// verify that prepublish runs on install, pack, and publish var common = require('../common-tap') var test = require('tap').test var fs = require('graceful-fs') @@ -38,7 +38,7 @@ test('setup', function (t) { } }) -test('test', function (t) { +test('prepublish deprecation warning on `npm pack`', function (t) { var env = { 'npm_config_cache': cache, 'npm_config_tmp': tmp, @@ -59,7 +59,7 @@ test('test', function (t) { t.equal(code, 0, 'pack finished successfully') t.ifErr(err, 'pack finished successfully') - t.notOk(stderr, 'got stderr data:' + JSON.stringify('' + stderr)) + t.match(stderr, /`prepublish` scripts will run only for `npm publish`/) var c = stdout.trim() var regex = new RegExp('' + '> npm-test-prepublish@1.2.5 prepublish [^\\r\\n]+\\r?\\n' + @@ -68,11 +68,45 @@ test('test', function (t) { 'ok\\r?\\n' + 'npm-test-prepublish-1.2.5.tgz', 'ig') - t.ok(c.match(regex)) + t.match(c, regex) t.end() }) }) +test('prepublish deprecation warning on `npm install`', function (t) { + var env = { + 'npm_config_cache': cache, + 'npm_config_tmp': tmp, + 'npm_config_prefix': pkg, + 'npm_config_global': 'false' + } + + for (var i in process.env) { + if (!/^npm_config_/.test(i)) { + env[i] = process.env[i] + } + } + + common.npm([ + 'install', + '--loglevel', 'warn' + ], { cwd: pkg, env: env }, function (err, code, stdout, stderr) { + t.equal(code, 0, 'pack finished successfully') + t.ifErr(err, 'pack finished successfully') + + t.match(stderr, /`prepublish` scripts will run only for `npm publish`/) + var c = stdout.trim() + var regex = new RegExp('' + + '> npm-test-prepublish@1.2.5 prepublish [^\\r\\n]+\\r?\\n' + + '> echo ok\\r?\\n' + + '\\r?\\n' + + 'ok' + + '', 'ig') + + t.match(c, regex) + t.end() + }) +}) test('cleanup', function (t) { cleanup() t.pass('cleaned up') diff --git a/deps/npm/test/tap/scope-header.js b/deps/npm/test/tap/scope-header.js new file mode 100644 index 0000000000..3eb70f132e --- /dev/null +++ b/deps/npm/test/tap/scope-header.js @@ -0,0 +1,160 @@ +'use strict' +var path = require('path') +var test = require('tap').test +var mr = require('npm-registry-mock') +var Tacks = require('tacks') +var File = Tacks.File +var Dir = Tacks.Dir +var common = require('../common-tap.js') + +var basedir = path.join(__dirname, path.basename(__filename, '.js')) +var testdir = path.join(basedir, 'testdir') +var withScope = path.join(testdir, 'with-scope') +var withoutScope = path.join(testdir, 'without-scope') +var onlyProjectScope = path.join(testdir, 'only-project-scope') +var cachedir = path.join(basedir, 'cache') +var globaldir = path.join(basedir, 'global') +var tmpdir = path.join(basedir, 'tmp') + +var env = common.newEnv().extend({ + npm_config_cache: cachedir, + npm_config_tmp: tmpdir, + npm_config_prefix: globaldir, + npm_config_registry: common.registry, + npm_config_loglevel: 'warn' +}) + +var conf = function (cwd) { + return { + cwd: cwd || testdir, + env: env + } +} +var server +var fixture = new Tacks(Dir({ + cache: Dir(), + global: Dir(), + tmp: Dir(), + testdir: Dir({ + 'with-scope': Dir({ + 'package.json': File({ + name: '@example/with-scope', + version: '1.0.0' + }) + }), + 'without-scope': Dir({ + 'package.json': File({ + name: 'without-scope', + version: '1.0.0' + }) + }), + 'only-project-scope': Dir({ + '.npmrc': File('@example:registry=thisisinvalid\n'), + 'package.json': File({ + name: '@example/only-project-scope', + version: '1.0.0' + }) + }) + }) +})) + +function setup () { + cleanup() + fixture.create(basedir) +} + +function cleanup () { + fixture.remove(basedir) +} + +var scopeHeaderTestGotScope = { + 'time': {'1.0.0': ''}, + 'dist-tags': {'latest': '1.0.0'}, + 'versions': { + '1.0.0': { + name: 'scope-header-test', + version: '1.0.0', + gotScope: true + } + } +} +var scopeHeaderTestNoScope = { + 'time': {'1.0.0': ''}, + 'dist-tags': {'latest': '1.0.0'}, + 'versions': { + '1.0.0': { + name: 'scope-header-test', + version: '1.0.0', + gotScope: false + } + } +} + +test('setup', function (t) { + setup() + mr({port: common.port, throwOnUnmatched: true}, function (err, s) { + if (err) throw err + server = s + server.get('/scope-header-test', { + 'NPM-Scope': '@example' + }).many().reply('200', scopeHeaderTestGotScope) + server.get('/scope-header-test', {}).many().reply('200', scopeHeaderTestNoScope) + t.done() + }) +}) + +test('with-scope', function (t) { + common.npm(['view', '--cache-min=0', 'scope-header-test', 'gotScope'], conf(withScope), function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stderr.trim()) + t.is(stdout.trim(), 'true', 'got version matched to scope header') + t.done() + }) +}) + +test('without-scope', function (t) { + common.npm(['view', '--cache-min=0', 'scope-header-test', 'gotScope'], conf(withoutScope), function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stderr.trim()) + t.is(stdout.trim(), 'false', 'got version matched to NO scope header') + t.done() + }) +}) + +test('scope can be forced by having an explicit one', function (t) { + common.npm([ + 'view', + '--cache-min=0', + '--scope=example', + 'scope-header-test', + 'gotScope' + ], conf(withoutScope), function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stderr.trim()) + t.is(stdout.trim(), 'true', 'got version matched to scope header') + t.done() + }) +}) + +test('only the project scope is used', function (t) { + common.npm([ + 'view', + '--cache-min=0', + 'scope-header-test', 'gotScope' + ], conf(onlyProjectScope), function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stderr.trim()) + t.is(stdout.trim(), 'true', 'got version scope') + t.done() + }) +}) + +test('cleanup', function (t) { + server.close() + cleanup() + t.done() +}) diff --git a/deps/npm/test/tap/search.js b/deps/npm/test/tap/search.js index 138517c3bd..dcc46df78e 100644 --- a/deps/npm/test/tap/search.js +++ b/deps/npm/test/tap/search.js @@ -1,81 +1,185 @@ -var fs = require('graceful-fs') var path = require('path') - var mkdirp = require('mkdirp') -var mr = require('npm-registry-mock') var osenv = require('osenv') var rimraf = require('rimraf') +var cacheFile = require('npm-cache-filename') var test = require('tap').test +var Tacks = require('tacks') +var File = Tacks.File var common = require('../common-tap.js') -var pkg = path.resolve(__dirname, 'search') -var cache = path.resolve(pkg, 'cache') -var registryCache = path.resolve(cache, 'localhost_1337', '-', 'all') -var cacheJsonFile = path.resolve(registryCache, '.cache.json') - -var timeMock = { - epoch: 1411727900, - future: 1411727900 + 100, - all: 1411727900 + 25, - since: 0 // filled by since server callback -} +var PKG_DIR = path.resolve(__dirname, 'search') +var CACHE_DIR = path.resolve(PKG_DIR, 'cache') +var cacheBase = cacheFile(CACHE_DIR)(common.registry + '/-/all') +var cachePath = path.join(cacheBase, '.cache.json') -var EXEC_OPTS = {} +test('setup', function (t) { + t.pass('all set up') + t.done() +}) -var mocks = { - /* Since request, always response with an _update time > the time requested */ - sinceFuture: function (server) { - server.filteringPathRegEx(/startkey=[^&]*/g, function (s) { - var _allMock = JSON.parse(JSON.stringify(allMock)) - timeMock.since = _allMock._updated = s.replace('startkey=', '') - server.get('/-/all/since?stale=update_after&' + s) - .reply(200, _allMock) - return s - }) - }, - allFutureUpdatedOnly: function (server) { - server.get('/-/all') - .reply(200, stringifyUpdated(timeMock.future)) - }, - all: function (server) { - server.get('/-/all') - .reply(200, allMock) +test('notifies when there are no results', function (t) { + setup() + var now = Date.now() + var cacheContents = { + '_updated': now, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } } -} + var fixture = new Tacks(File(cacheContents)) + fixture.create(cachePath) + common.npm([ + 'search', 'nomatcheswhatsoeverfromthis', + '--registry', common.registry, + '--loglevel', 'error', + '--cache', CACHE_DIR + ], {}, function (err, code, stdout, stderr) { + if (err) throw err + t.equal(stderr, '', 'no error output') + t.equal(code, 0, 'search gives 0 error code even if no matches') + t.match(stdout, /No matches found/, 'Useful message on search failure') + t.done() + }) +}) -test('No previous cache, init cache triggered by first search', function (t) { - cleanup() +test('spits out a useful error when no cache nor network', function (t) { + setup() + var cacheContents = {} + var fixture = new Tacks(File(cacheContents)) + fixture.create(cachePath) + common.npm([ + 'search', 'foo', + '--registry', common.registry, + '--loglevel', 'silly', + '--fetch-retry-mintimeout', 0, + '--fetch-retry-maxtimeout', 0, + '--cache', CACHE_DIR + ], {}, function (err, code, stdout, stderr) { + if (err) throw err + t.equal(code, 1, 'non-zero exit code') + t.equal(stdout, '', 'no stdout output') + t.match(stderr, /No search sources available/, 'useful error') + t.done() + }) +}) - mr({ port: common.port, plugin: mocks.allFutureUpdatedOnly }, function (err, s) { - t.ifError(err, 'mock registry started') - common.npm([ - 'search', 'do not do extra search work on my behalf', - '--registry', common.registry, - '--cache', cache, - '--loglevel', 'silent', - '--color', 'always' - ], - EXEC_OPTS, - function (err, code) { - s.close() - t.equal(code, 0, 'search finished successfully') - t.ifErr(err, 'search finished successfully') +test('can switch to JSON mode', function (t) { + setup() + var now = Date.now() + var cacheContents = { + '_updated': now, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + } + var fixture = new Tacks(File(cacheContents)) + fixture.create(cachePath) + common.npm([ + 'search', 'oo', + '--json', + '--registry', common.registry, + '--loglevel', 'error', + '--cache', CACHE_DIR + ], {}, function (err, code, stdout, stderr) { + if (err) throw err + t.deepEquals(JSON.parse(stdout), [ + { name: 'cool', version: '1.0.0' }, + { name: 'foo', version: '2.0.0' } + ], 'results returned as valid json') + t.equal(stderr, '', 'no error output') + t.equal(code, 0, 'search gives 0 error code even if no matches') + t.done() + }) +}) - t.ok( - fs.existsSync(cacheJsonFile), - cacheJsonFile + ' expected to have been created' - ) +test('JSON mode does not notify on empty', function (t) { + setup() + var now = Date.now() + var cacheContents = { + '_updated': now, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + } + var fixture = new Tacks(File(cacheContents)) + fixture.create(cachePath) + common.npm([ + 'search', 'nomatcheswhatsoeverfromthis', + '--json', + '--registry', common.registry, + '--loglevel', 'error', + '--cache', CACHE_DIR + ], {}, function (err, code, stdout, stderr) { + if (err) throw err + t.deepEquals(JSON.parse(stdout), [], 'no notification about no results') + t.equal(stderr, '', 'no error output') + t.equal(code, 0, 'search gives 0 error code even if no matches') + t.done() + }) +}) - var cacheData = JSON.parse(fs.readFileSync(cacheJsonFile, 'utf8')) - t.equal(cacheData._updated, String(timeMock.future)) - t.end() - }) +test('can switch to tab separated mode', function (t) { + setup() + var now = Date.now() + var cacheContents = { + '_updated': now, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', description: 'this\thas\ttabs', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + } + var fixture = new Tacks(File(cacheContents)) + fixture.create(cachePath) + common.npm([ + 'search', 'oo', + '--parseable', + '--registry', common.registry, + '--loglevel', 'error', + '--cache', CACHE_DIR + ], {}, function (err, code, stdout, stderr) { + if (err) throw err + t.equal(stdout, 'cool\t\t\tprehistoric\t\t\nfoo\tthis has tabs\t\tprehistoric\t\t\n', 'correct output, including replacing tabs in descriptions') + t.equal(stderr, '', 'no error output') + t.equal(code, 0, 'search gives 0 error code even if no matches') + t.done() + }) +}) + +test('tab mode does not notify on empty', function (t) { + setup() + var now = Date.now() + var cacheContents = { + '_updated': now, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '1.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + } + var fixture = new Tacks(File(cacheContents)) + fixture.create(cachePath) + common.npm([ + 'search', 'nomatcheswhatsoeverfromthis', + '--parseable', + '--registry', common.registry, + '--loglevel', 'error', + '--cache', CACHE_DIR + ], {}, function (err, code, stdout, stderr) { + if (err) throw err + t.equal(stdout, '', 'no notification about no results') + t.equal(stderr, '', 'no error output') + t.equal(code, 0, 'search gives 0 error code even if no matches') + t.done() }) }) test('no arguments provided should error', function (t) { - common.npm(['search'], [], function (err, code, stdout, stderr) { + cleanup() + common.npm(['search'], {}, function (err, code, stdout, stderr) { if (err) throw err t.equal(code, 1, 'search finished unsuccessfully') @@ -88,83 +192,157 @@ test('no arguments provided should error', function (t) { }) }) -test('previous cache, _updated set, should trigger since request', function (t) { - setupCache() - - function m (server) { - [ mocks.all, mocks.sinceFuture ].forEach(function (m) { - m(server) - }) - } - mr({ port: common.port, plugin: m }, function (err, s) { - t.ifError(err, 'mock registry started') - common.npm([ - 'search', 'do not do extra search work on my behalf', - '--registry', common.registry, - '--cache', cache, - '--loglevel', 'silly', - '--color', 'always' - ], - EXEC_OPTS, - function (err, code) { - s.close() - t.equal(code, 0, 'search finished successfully') - t.ifErr(err, 'search finished successfully') - - var cacheData = JSON.parse(fs.readFileSync(cacheJsonFile, 'utf8')) - t.equal( - cacheData._updated, - timeMock.since, - 'cache update time gotten from since response' - ) - t.end() - }) - }) -}) - var searches = [ { - term: 'f36b6a6123da50959741e2ce4d634f96ec668c56', - description: 'non-regex', - location: 241 + term: 'cool', + description: 'non-regex search', + location: 103 + }, + { + term: '/cool/', + description: 'regex search', + location: 103 }, { - term: '/f36b6a6123da50959741e2ce4d634f96ec668c56/', - description: 'regex', - location: 241 + term: 'cool', + description: 'searches name field', + location: 103 + }, + { + term: 'ool', + description: 'excludes matches for --searchexclude', + location: 205, + inject: { + other: { name: 'other', description: 'this is a simple tool' } + }, + extraOpts: ['--searchexclude', 'cool'] + }, + { + term: 'neat lib', + description: 'searches description field', + location: 141, + inject: { + cool: { + name: 'cool', version: '5.0.0', description: 'this is a neat lib' + } + } + }, + { + term: 'foo', + description: 'skips description field with --no-description', + location: 80, + inject: { + cool: { + name: 'cool', version: '5.0.0', description: 'foo bar!' + } + }, + extraOpts: ['--no-description'] + }, + { + term: 'zkat', + description: 'searches maintainers by name', + location: 155, + inject: { + cool: { + name: 'cool', + version: '5.0.0', + maintainers: [{ + name: 'zkat' + }] + } + } + }, + { + term: '=zkat', + description: 'searches maintainers unambiguously by =name', + location: 154, + inject: { + bar: { name: 'bar', description: 'zkat thing', version: '1.0.0' }, + cool: { + name: 'cool', + version: '5.0.0', + maintainers: [{ + name: 'zkat' + }] + } + } + }, + { + term: 'github.com', + description: 'searches projects by url', + location: 205, + inject: { + bar: { + name: 'bar', + url: 'gitlab.com/bar', + // For historical reasons, `url` is only present if `versions` is there + versions: ['1.0.0'], + version: '1.0.0' + }, + cool: { + name: 'cool', + version: '5.0.0', + versions: ['1.0.0'], + url: 'github.com/cool/cool' + } + } + }, + { + term: 'monad', + description: 'searches projects by keywords', + location: 197, + inject: { + cool: { + name: 'cool', + version: '5.0.0', + keywords: ['monads'] + } + } } ] searches.forEach(function (search) { - test(search.description + ' search in color', function (t) { - cleanup() - mr({ port: common.port, plugin: mocks.all }, function (er, s) { - common.npm([ - 'search', search.term, - '--registry', common.registry, - '--cache', cache, - '--loglevel', 'silent', - '--color', 'always' - ], - EXEC_OPTS, - function (err, code, stdout) { - s.close() - t.equal(code, 0, 'search finished successfully') - t.ifErr(err, 'search finished successfully') - // \033 == \u001B - var markStart = '\u001B\\[[0-9][0-9]m' - var markEnd = '\u001B\\[0m' + test(search.description, function (t) { + setup() + var now = Date.now() + var cacheContents = { + '_updated': now, + bar: { name: 'bar', version: '1.0.0' }, + cool: { name: 'cool', version: '5.0.0' }, + foo: { name: 'foo', version: '2.0.0' }, + other: { name: 'other', version: '1.0.0' } + } + for (var k in search.inject) { + cacheContents[k] = search.inject[k] + } + var fixture = new Tacks(File(cacheContents)) + fixture.create(cachePath) + common.npm([ + 'search', search.term, + '--registry', common.registry, + '--cache', CACHE_DIR, + '--loglevel', 'error', + '--color', 'always' + ].concat(search.extraOpts || []), + {}, + function (err, code, stdout, stderr) { + t.equal(stderr, '', 'no error output') + t.notEqual(stdout, '', 'got output') + t.equal(code, 0, 'search finished successfully') + t.ifErr(err, 'search finished successfully') + // \033 == \u001B + var markStart = '\u001B\\[[0-9][0-9]m' + var markEnd = '\u001B\\[0m' - var re = new RegExp(markStart + '.*?' + markEnd) + var re = new RegExp(markStart + '.*?' + markEnd) - var cnt = stdout.search(re) - t.equal( - cnt, - search.location, - search.description + ' search for ' + search.term - ) - t.end() - }) + var cnt = stdout.search(re) + t.equal( + cnt, + search.location, + search.description + ' search for ' + search.term + ) + t.end() }) }) }) @@ -174,117 +352,12 @@ test('cleanup', function (t) { t.end() }) -function cleanup () { - process.chdir(osenv.tmpdir()) - rimraf.sync(pkg) -} - -function setupCache () { +function setup () { cleanup() - mkdirp.sync(cache) - mkdirp.sync(registryCache) - var res = fs.writeFileSync(cacheJsonFile, stringifyUpdated(timeMock.epoch)) - if (res) throw new Error('Creating cache file failed') + mkdirp.sync(cacheBase) } -function stringifyUpdated (time) { - return JSON.stringify({ _updated: String(time) }) -} - -var allMock = { - '_updated': timeMock.all, - 'generator-frontcow': { - 'name': 'generator-frontcow', - 'description': 'f36b6a6123da50959741e2ce4d634f96ec668c56 This is a fake description to ensure we do not accidentally search the real npm registry or use some kind of cache', - 'dist-tags': { - 'latest': '0.1.19' - }, - 'maintainers': [ - { - 'name': 'bcabanes', - 'email': 'contact@benjamincabanes.com' - } - ], - 'homepage': 'https://github.com/bcabanes/generator-frontcow', - 'keywords': [ - 'sass', - 'frontend', - 'yeoman-generator', - 'atomic', - 'design', - 'sass', - 'foundation', - 'foundation5', - 'atomic design', - 'bourbon', - 'polyfill', - 'font awesome' - ], - 'repository': { - 'type': 'git', - 'url': 'https://github.com/bcabanes/generator-frontcow' - }, - 'author': { - 'name': 'ben', - 'email': 'contact@benjamincabanes.com', - 'url': 'https://github.com/bcabanes' - }, - 'bugs': { - 'url': 'https://github.com/bcabanes/generator-frontcow/issues' - }, - 'license': 'MIT', - 'readmeFilename': 'README.md', - 'time': { - 'modified': '2014-10-03T02:26:18.406Z' - }, - 'versions': { - '0.1.19': 'latest' - } - }, - 'marko': { - 'name': 'marko', - 'description': 'Marko is an extensible, streaming, asynchronous, high performance, HTML-based templating language that can be used in Node.js or in the browser.', - 'dist-tags': { - 'latest': '1.2.16' - }, - 'maintainers': [ - { - 'name': 'pnidem', - 'email': 'pnidem@gmail.com' - }, - { - 'name': 'philidem', - 'email': 'phillip.idem@gmail.com' - } - ], - 'homepage': 'https://github.com/raptorjs/marko', - 'keywords': [ - 'templating', - 'template', - 'async', - 'streaming' - ], - 'repository': { - 'type': 'git', - 'url': 'https://github.com/raptorjs/marko.git' - }, - 'author': { - 'name': 'Patrick Steele-Idem', - 'email': 'pnidem@gmail.com' - }, - 'bugs': { - 'url': 'https://github.com/raptorjs/marko/issues' - }, - 'license': 'Apache License v2.0', - 'readmeFilename': 'README.md', - 'users': { - 'pnidem': true - }, - 'time': { - 'modified': '2014-10-03T02:27:31.775Z' - }, - 'versions': { - '1.2.16': 'latest' - } - } +function cleanup () { + process.chdir(osenv.tmpdir()) + rimraf.sync(PKG_DIR) } diff --git a/deps/npm/test/tap/semver-tag.js b/deps/npm/test/tap/semver-tag.js deleted file mode 100644 index b4feb75176..0000000000 --- a/deps/npm/test/tap/semver-tag.js +++ /dev/null @@ -1,15 +0,0 @@ -// should not allow tagging with a valid semver range -var common = require('../common-tap.js') -var test = require('tap').test - -test('try to tag with semver range as tag name', function (t) { - var cmd = ['tag', 'zzzz@1.2.3', 'v2.x', '--registry=http://localhost'] - common.npm(cmd, { - stdio: 'pipe' - }, function (er, code, so, se) { - if (er) throw er - t.similar(se, /Tag name must not be a valid SemVer range: v2.x\n/) - t.equal(code, 1) - t.end() - }) -}) diff --git a/deps/npm/test/tap/shrinkwrap-complete-except-dev.js b/deps/npm/test/tap/shrinkwrap-complete-except-dev.js new file mode 100644 index 0000000000..156721507f --- /dev/null +++ b/deps/npm/test/tap/shrinkwrap-complete-except-dev.js @@ -0,0 +1,159 @@ +'use strict' +var fs = require('graceful-fs') +var path = require('path') +var test = require('tap').test +var mr = require('npm-registry-mock') +var Tacks = require('tacks') +var File = Tacks.File +var Dir = Tacks.Dir +var extend = Object.assign || require('util')._extend +var common = require('../common-tap.js') + +var basedir = path.join(__dirname, path.basename(__filename, '.js')) +var testdir = path.join(basedir, 'testdir') +var cachedir = path.join(basedir, 'cache') +var globaldir = path.join(basedir, 'global') +var tmpdir = path.join(basedir, 'tmp') +var appdir = path.join(testdir, 'app') +var installedModuleA = path.join(appdir, 'node_modules', 'module-a') +var installedModuleB = path.join(installedModuleA, 'node_modules', 'module-b') +var installedModuleC = path.join(appdir, 'node_modules', 'module-c') +var installedModuleD = path.join(appdir, 'node_modules', 'module-d') + +var conf = { + cwd: appdir, + env: extend(extend({}, process.env), { + npm_config_cache: cachedir, + npm_config_tmp: tmpdir, + npm_config_prefix: globaldir, + npm_config_registry: common.registry, + npm_config_loglevel: 'warn' + }) +} + +var server +var fixture = new Tacks(Dir({ + cache: Dir(), + global: Dir(), + tmp: Dir(), + testdir: Dir({ + app: Dir({ + 'npm-shrinkwrap.json': File({ + name: 'app', + version: '1.0.0', + dependencies: { + 'module-a': { + version: '1.0.0', + from: '../module-a', + resolved: 'file:../module-a', + dependencies: { + 'module-b': { + version: '1.0.0', + from: '../module-b', + resolved: 'file:../module-b' + } + } + } + } + }), + 'package.json': File({ + name: 'app', + version: '1.0.0', + dependencies: { + 'module-a': '../module-a' + }, + devDependencies: { + 'module-d': '../module-d' + } + }) + }), + 'module-a': Dir({ + 'package.json': File({ + name: 'module-a', + version: '1.0.0', + dependencies: { + 'module-b': 'file:' + path.join(testdir, 'module-b'), + 'module-c': 'file:' + path.join(testdir, 'module-c') + } + }) + }), + 'module-b': Dir({ + 'package.json': File({ + name: 'module-b', + version: '1.0.0' + }) + }), + 'module-c': Dir({ + 'package.json': File({ + name: 'module-c', + version: '1.0.0' + }) + }), + 'module-d': Dir({ + 'package.json': File({ + name: 'module-d', + version: '1.0.0' + }) + }) + }) +})) + +function exists (t, filepath, msg) { + try { + fs.statSync(filepath) + t.pass(msg) + return true + } catch (ex) { + t.fail(msg, {found: ex, wanted: 'exists', compare: 'fs.stat(' + filepath + ')'}) + return false + } +} + +function notExists (t, filepath, msg) { + try { + fs.statSync(filepath) + t.fail(msg, {found: true, wanted: 'does not exist', compare: 'fs.stat(' + filepath + ')'}) + return true + } catch (ex) { + t.pass(msg) + return false + } +} + +function setup () { + cleanup() + fixture.create(basedir) +} + +function cleanup () { + fixture.remove(basedir) +} + +test('setup', function (t) { + setup() + mr({port: common.port, throwOnUnmatched: true}, function (err, s) { + if (err) throw err + server = s + t.done() + }) +}) + +test('example', function (t) { + common.npm(['install'], conf, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment('1> ' + stdout.trim()) + t.comment('2> ' + stderr.trim()) + exists(t, installedModuleA, 'module-a: dep in shrinkwrap installed') + exists(t, installedModuleB, 'module-b: nested dep from shrinkwrap nested') + notExists(t, installedModuleC, 'module-c: dependency not installed because not in shrinkwrap') + exists(t, installedModuleD, 'module-d: dev dependency installed despite not being in shrinkwrap') + t.done() + }) +}) + +test('cleanup', function (t) { + server.close() + cleanup() + t.done() +}) diff --git a/deps/npm/test/tap/shrinkwrap-default-dev.js b/deps/npm/test/tap/shrinkwrap-default-dev.js new file mode 100644 index 0000000000..6b1aa181e1 --- /dev/null +++ b/deps/npm/test/tap/shrinkwrap-default-dev.js @@ -0,0 +1,125 @@ +'use strict' +var path = require('path') +var test = require('tap').test +var fs = require('fs') +var Tacks = require('tacks') +var File = Tacks.File +var Dir = Tacks.Dir +var extend = Object.assign || require('util')._extend +var common = require('../common-tap.js') + +var basedir = path.join(__dirname, path.basename(__filename, '.js')) +var testdir = path.join(basedir, 'testdir') +var cachedir = path.join(basedir, 'cache') +var globaldir = path.join(basedir, 'global') +var tmpdir = path.join(basedir, 'tmp') + +var conf = { + cwd: testdir, + env: extend(extend({}, process.env), { + npm_config_cache: cachedir, + npm_config_tmp: tmpdir, + npm_config_prefix: globaldir, + npm_config_registry: common.registry, + npm_config_loglevel: 'warn' + }) +} + +var fixture = new Tacks(Dir({ + cache: Dir(), + global: Dir(), + tmp: Dir(), + testdir: Dir({ + node_modules: Dir({ + '@npmtest': Dir({ + example: Dir({ + 'package.json': File({ + name: '@npmtest/example', + version: '1.0.0' + }) + }) + }) + }), + 'package.json': File({ + name: 'shrinkwrap-default-dev', + version: '1.0.0', + devDependencies: { + '@npmtest/example': '1.0.0' + } + }) + }) +})) + +var shrinkwrapPath = path.join(testdir, 'npm-shrinkwrap.json') +var shrinkwrapWithDev = { + name: 'shrinkwrap-default-dev', + version: '1.0.0', + dependencies: { + '@npmtest/example': { + 'version': '1.0.0', + 'dev': true + } + } +} +var shrinkwrapWithoutDev = { + name: 'shrinkwrap-default-dev', + version: '1.0.0', + dependencies: {} +} + +function setup () { + cleanup() + fixture.create(basedir) +} + +function cleanup () { + fixture.remove(basedir) +} + +test('setup', function (t) { + setup() + t.done() +}) + +test('shrinkwrap-default-dev', function (t) { + common.npm(['shrinkwrap'], conf, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stdout.trim()) + t.comment(stderr.trim()) + var swrap = JSON.parse(fs.readFileSync(shrinkwrapPath)) + t.isDeeply(swrap, shrinkwrapWithDev, 'Shrinkwrap included dev deps by default') + t.done() + }) +}) + +test('shrinkwrap-only-prod', function (t) { + fs.unlinkSync(shrinkwrapPath) + common.npm(['shrinkwrap', '--only=prod'], conf, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stdout.trim()) + t.comment(stderr.trim()) + var swrap = JSON.parse(fs.readFileSync(shrinkwrapPath)) + t.isDeeply(swrap, shrinkwrapWithoutDev, 'Shrinkwrap did not include dev deps with --only=prod') + t.done() + }) +}) + +test('shrinkwrap-production', function (t) { + fs.unlinkSync(shrinkwrapPath) + common.npm(['shrinkwrap', '--production'], conf, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stdout.trim()) + t.comment(stderr.trim()) + var swrap = JSON.parse(fs.readFileSync(shrinkwrapPath)) + t.isDeeply(swrap, shrinkwrapWithoutDev, 'Shrinkwrap did not include dev deps with --production') + t.done() + }) +}) + +test('cleanup', function (t) { + cleanup() + t.done() +}) diff --git a/deps/npm/test/tap/shrinkwrap-dev-dep-cycle.js b/deps/npm/test/tap/shrinkwrap-dev-dep-cycle.js index c9511bb152..cd5237e161 100644 --- a/deps/npm/test/tap/shrinkwrap-dev-dep-cycle.js +++ b/deps/npm/test/tap/shrinkwrap-dev-dep-cycle.js @@ -76,7 +76,7 @@ test('setup', function (t) { }) test('shrinkwrap cycle in dev deps', function (t) { - common.npm(['shrinkwrap'], {cwd: testdir}, function (err, code, stdout, stderr) { + common.npm(['shrinkwrap', '--only=prod'], {cwd: testdir}, function (err, code, stdout, stderr) { if (err) throw err t.is(code, 0, 'result code = ok') t.comment(stdout.trim()) diff --git a/deps/npm/test/tap/shrinkwrap-shared-dev-dependency.js b/deps/npm/test/tap/shrinkwrap-shared-dev-dependency.js index a6cddae33d..0011f7b6f1 100644 --- a/deps/npm/test/tap/shrinkwrap-shared-dev-dependency.js +++ b/deps/npm/test/tap/shrinkwrap-shared-dev-dependency.js @@ -21,6 +21,7 @@ test("shrinkwrap doesn't strip out the shared dependency", function (t) { npm.install('.', function (err) { if (err) return t.fail(err) + npm.config.set('dev', true) // npm install unsets this npm.commands.shrinkwrap([], true, function (err, results) { if (err) return t.fail(err) diff --git a/deps/npm/test/tap/shrinkwrap-transitive-dev.js b/deps/npm/test/tap/shrinkwrap-transitive-dev.js index 80a38f7f9e..7a8f5b45d2 100644 --- a/deps/npm/test/tap/shrinkwrap-transitive-dev.js +++ b/deps/npm/test/tap/shrinkwrap-transitive-dev.js @@ -62,7 +62,7 @@ test('setup', function (t) { }) test('transitive-deps-of-dev-deps', function (t) { - common.npm(['shrinkwrap', '--loglevel=error'], {cwd: testdir}, function (err, code, stdout, stderr) { + common.npm(['shrinkwrap', '--loglevel=error', '--only=prod'], {cwd: testdir}, function (err, code, stdout, stderr) { if (err) throw err t.is(code, 0, 'shrinkwrap ran ok') t.comment(stdout.trim()) diff --git a/deps/npm/test/tap/shrinkwrap-version-match.js b/deps/npm/test/tap/shrinkwrap-version-match.js index 7d9fed729a..bdcff2e4b6 100644 --- a/deps/npm/test/tap/shrinkwrap-version-match.js +++ b/deps/npm/test/tap/shrinkwrap-version-match.js @@ -7,6 +7,10 @@ var fs = require('fs') var path = require('path') var common = require('../common-tap.js') +// NOTE: This test will only remain relavent until npm@5 when +// npm-shrinkwrap.json will override EVERYTHING in you package.json. +// If you're working on npm@5 and this broke, just remove the test. + var testdir = path.resolve(__dirname, path.basename(__filename, '.js')) var modAdir = path.resolve(testdir, 'modA') var modB1dir = path.resolve(testdir, 'modB@1') @@ -16,7 +20,9 @@ var modCdir = path.resolve(testdir, 'modC') var fixture = new Tacks(Dir({ 'package.json': File({ dependencies: { - modA: 'file://' + modAdir, + modA: 'file://' + modAdir + }, + devDependencies: { modC: 'file://' + modCdir } }), diff --git a/deps/npm/test/tap/uninstall-in-reverse.js b/deps/npm/test/tap/uninstall-in-reverse.js index 2d8fe2bcfc..e5c7a80c7e 100644 --- a/deps/npm/test/tap/uninstall-in-reverse.js +++ b/deps/npm/test/tap/uninstall-in-reverse.js @@ -10,7 +10,7 @@ order. That is, they need to go shallow -> deep. var removed = [] var npm = requireInject.installGlobally('../../lib/npm.js', { - '../../lib/install/action/remove.js': function (top, buildpath, pkg, log, next) { + '../../lib/install/action/remove.js': function (staging, pkg, log, next) { removed.push(pkg.package.name) next() } diff --git a/deps/npm/test/tap/unit-deps-earliestInstallable.js b/deps/npm/test/tap/unit-deps-earliestInstallable.js new file mode 100644 index 0000000000..26a251addc --- /dev/null +++ b/deps/npm/test/tap/unit-deps-earliestInstallable.js @@ -0,0 +1,111 @@ +'use strict' +var test = require('tap').test +var requireInject = require('require-inject') + +// we're just mocking to avoid having to call `npm.load` +var deps = requireInject('../../lib/install/deps.js', { + '../../lib/npm.js': { + config: { + get: function (val) { return (val === 'global-style' || val === 'legacy-bundling') ? false : 'mock' } + } + } +}) + +var earliestInstallable = deps.earliestInstallable + +test('earliestInstallable should consider devDependencies', function (t) { + var dep1 = { + children: [], + package: { + name: 'dep1', + dependencies: { dep2: '2.0.0' } + } + } + + // a library required by the base package + var dep2 = { + package: { + name: 'dep2', + version: '1.0.0' + } + } + + // an incompatible verson of dep2. required by dep1 + var dep2a = { + package: { + name: 'dep2', + version: '2.0.0', + _from: { + raw: 'dep2@1.0.0', + scope: null, + escapedName: 'dep2', + name: 'dep2', + rawSpec: '1.0.0', + spec: '1.0.0', + type: 'version' + } + }, + parent: dep1 + } + + var pkg = { + isTop: true, + children: [dep1], + package: { + name: 'pkg', + dependencies: { dep1: '1.0.0' }, + devDependencies: { dep2: '1.0.0' } + } + } + + dep1.parent = pkg + dep2a.parent = dep1 + dep2.parent = pkg + + var earliest = earliestInstallable(dep1, dep1, dep2a.package) + t.isDeeply(earliest, dep1, 'should hoist package when an incompatible devDependency is present') + t.end() +}) + +test('earliestInstallable should reuse shared prod/dev deps when they are identical', function (t) { + var dep1 = { + children: [], + package: { + name: 'dep1', + dependencies: { dep2: '1.0.0' } + } + } + + var dep2 = { + package: { + name: 'dep2', + version: '1.0.0', + _from: { + raw: 'dep2@^1.0.0', + scope: null, + escapedName: 'dep2', + name: 'dep2', + rawSpec: '^1.0.0', + spec: '>=1.0.0 <2.0.0', + type: 'range' + } + } + } + + var pkg = { + isTop: true, + children: [dep1], + package: { + name: 'pkg', + dependencies: { dep1: '1.0.0' }, + devDependencies: { dep2: '^1.0.0' } + } + } + + dep1.parent = pkg + dep2.parent = pkg + + var earliest = earliestInstallable(dep1, dep1, dep2.package) + t.isDeeply(earliest, pkg, 'should reuse identical shared dev/prod deps when installing both') + t.end() +}) diff --git a/deps/npm/test/tap/unsupported.js b/deps/npm/test/tap/unsupported.js new file mode 100644 index 0000000000..dff86a6231 --- /dev/null +++ b/deps/npm/test/tap/unsupported.js @@ -0,0 +1,41 @@ +'use strict' +var test = require('tap').test +var unsupported = require('../../lib/utils/unsupported.js') + +var versions = [ + // broken unsupported + ['v0.1.103', true, true], + ['v0.2.0', true, true], + ['v0.3.5', true, true], + ['v0.4.7', true, true], + ['v0.5.3', true, true], + ['v0.6.17', true, true], + ['v0.7.8', true, true], + ['v0.8.28', false, true], + ['v0.9.6', false, true], + ['v0.10.48', false, true], + ['v0.11.16', false, true], + ['v0.12.9', false, false], + ['v1.0.1', false, true], + ['v1.6.0', false, true], + ['v2.3.1', false, true], + ['v3.0.0', false, true], + ['v4.5.0', false, false], + ['v5.7.1', false, false], + ['v6.8.1', false, false], + ['v7.0.0-beta23', false, false], + ['v7.2.3', false, false] +] + +test('versions', function (t) { + t.plan(versions.length * 2) + versions.forEach(function (verinfo) { + var version = verinfo[0] + var broken = verinfo[1] + var unsupp = verinfo[2] + var nodejs = unsupported.checkVersion(version) + t.is(nodejs.broken, broken, version + ' ' + (broken ? '' : 'not ') + 'broken') + t.is(nodejs.unsupported, unsupp, version + ' ' + (unsupp ? 'unsupported' : 'supported')) + }) + t.done() +}) diff --git a/deps/npm/test/tap/update-index.js b/deps/npm/test/tap/update-index.js deleted file mode 100644 index 91b2f78781..0000000000 --- a/deps/npm/test/tap/update-index.js +++ /dev/null @@ -1,195 +0,0 @@ -var common = require('../common-tap.js') -var test = require('tap').test -var npm = require('../../') -var mkdirp = require('mkdirp') -var rimraf = require('rimraf') -var path = require('path') -var mr = require('npm-registry-mock') - -var updateIndex = require('../../lib/cache/update-index.js') - -var PKG_DIR = path.resolve(__dirname, 'get-basic') -var CACHE_DIR = path.resolve(PKG_DIR, 'cache') - -var server - -var mocks = { - basic: function (mock) { - mock.get('/-/all').reply(200, allMock) - }, - auth: function (mock) { - var littleBobbyTablesAuth = new Buffer('bobby:tables').toString('base64') - var auth = 'Basic ' + littleBobbyTablesAuth - mock.get('/-/all', { authorization: auth }).reply(200, allMock) - mock.get('/-/all').reply(401, { - error: 'unauthorized', - reason: 'You are not authorized to access this db.' - }) - } -} - -var allMock = { - '_updated': 1411727900 + 25, - 'generator-frontcow': { - 'name': 'generator-frontcow', - 'description': 'f36b6a6123da50959741e2ce4d634f96ec668c56 This is a fake description to ensure we do not accidentally search the real npm registry or use some kind of cache', - 'dist-tags': { - 'latest': '0.1.19' - }, - 'maintainers': [ - { - 'name': 'bcabanes', - 'email': 'contact@benjamincabanes.com' - } - ], - 'homepage': 'https://github.com/bcabanes/generator-frontcow', - 'keywords': [ - 'sass', - 'frontend', - 'yeoman-generator', - 'atomic', - 'design', - 'sass', - 'foundation', - 'foundation5', - 'atomic design', - 'bourbon', - 'polyfill', - 'font awesome' - ], - 'repository': { - 'type': 'git', - 'url': 'https://github.com/bcabanes/generator-frontcow' - }, - 'author': { - 'name': 'ben', - 'email': 'contact@benjamincabanes.com', - 'url': 'https://github.com/bcabanes' - }, - 'bugs': { - 'url': 'https://github.com/bcabanes/generator-frontcow/issues' - }, - 'license': 'MIT', - 'readmeFilename': 'README.md', - 'time': { - 'modified': '2014-10-03T02:26:18.406Z' - }, - 'versions': { - '0.1.19': 'latest' - } - }, - 'marko': { - 'name': 'marko', - 'description': 'Marko is an extensible, streaming, asynchronous, high performance, HTML-based templating language that can be used in Node.js or in the browser.', - 'dist-tags': { - 'latest': '1.2.16' - }, - 'maintainers': [ - { - 'name': 'pnidem', - 'email': 'pnidem@gmail.com' - }, - { - 'name': 'philidem', - 'email': 'phillip.idem@gmail.com' - } - ], - 'homepage': 'https://github.com/raptorjs/marko', - 'keywords': [ - 'templating', - 'template', - 'async', - 'streaming' - ], - 'repository': { - 'type': 'git', - 'url': 'https://github.com/raptorjs/marko.git' - }, - 'author': { - 'name': 'Patrick Steele-Idem', - 'email': 'pnidem@gmail.com' - }, - 'bugs': { - 'url': 'https://github.com/raptorjs/marko/issues' - }, - 'license': 'Apache License v2.0', - 'readmeFilename': 'README.md', - 'users': { - 'pnidem': true - }, - 'time': { - 'modified': '2014-10-03T02:27:31.775Z' - }, - 'versions': { - '1.2.16': 'latest' - } - } -} - -function setup (t, mock, extra) { - mkdirp.sync(CACHE_DIR) - mr({ port: common.port, plugin: mock }, function (er, s) { - npm.load({ cache: CACHE_DIR, registry: common.registry }, function (err) { - if (extra) { - Object.keys(extra).forEach(function (k) { - npm.config.set(k, extra[k], 'user') - }) - } - t.ifError(err, 'no error') - server = s - t.end() - }) - }) -} - -function cleanup (t) { - server.close(function () { - rimraf.sync(PKG_DIR) - - t.end() - }) -} - -test('setup basic', function (t) { - setup(t, mocks.basic) -}) - -test('request basic', function (t) { - updateIndex(0, function (er) { - t.ifError(er, 'no error') - t.end() - }) -}) - -test('cleanup basic', cleanup) - -test('setup auth', function (t) { - setup(t, mocks.auth) -}) - -test('request auth failure', function (t) { - updateIndex(0, function (er) { - t.equals(er.code, 'E401', 'gotta get that auth') - t.ok(/^unauthorized/.test(er.message), 'unauthorized message') - t.end() - }) -}) - -test('cleanup auth failure', cleanup) - -test('setup auth', function (t) { - // mimic as if alwaysAuth had been set - setup(t, mocks.auth, { - _auth: new Buffer('bobby:tables').toString('base64'), - 'always-auth': true - }) -}) - -test('request auth success', function (t) { - updateIndex(0, function (er) { - t.ifError(er, 'no error') - t.end() - }) -}) - -test('cleanup auth', cleanup) |