diff options
author | Rebecca Turner <me@re-becca.org> | 2017-04-07 01:24:31 +0300 |
---|---|---|
committer | Rebecca Turner <me@re-becca.org> | 2017-05-26 04:55:03 +0300 |
commit | c0a30c0725070dc49ac9e004f9e33bac68c69e20 (patch) | |
tree | edeb150fec2c8a318086c8ec18aff544655baf5e /test | |
parent | d61b65d26d054c24af37366461e0275073e7739f (diff) |
test: Spec tests for local specifiers
Diffstat (limited to 'test')
-rw-r--r-- | test/tap/spec-local-specifiers.js | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/test/tap/spec-local-specifiers.js b/test/tap/spec-local-specifiers.js new file mode 100644 index 000000000..608d5503a --- /dev/null +++ b/test/tap/spec-local-specifiers.js @@ -0,0 +1,629 @@ +'use strict' +var path = require('path') +var test = require('tap').test +var fs = require('fs') +var rimraf = require('rimraf') +var mr = require('npm-registry-mock') +var Tacks = require('tacks') +var File = Tacks.File +var Symlink = Tacks.Symlink +var Dir = Tacks.Dir +var common = require('../common-tap.js') +var isWindows = require('../../lib/utils/is-windows.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: common.newEnv().extend({ + npm_config_cache: cachedir, + npm_config_tmp: tmpdir, + npm_config_prefix: globaldir, + npm_config_registry: common.registry, + npm_config_loglevel: 'error' + }), + stdio: [0, 'pipe', 2] +} +var confE = { + cwd: testdir, + env: conf.env, + stdio: [0, 'pipe', 'pipe'] +} + +function readJson (file) { + return JSON.parse(fs.readFileSync(file)) +} + +function isSymlink (t, file, message) { + try { + var info = fs.lstatSync(file) + t.is(info.isSymbolicLink(), true, message) + } catch (ex) { + if (ex.code === 'ENOENT') { + t.fail(message, {found: null, wanted: 'symlink', compare: 'fs.lstat(' + file + ')'}) + } else { + t.fail(message, {found: ex, wanted: 'symlink', compare: 'fs.lstat(' + file + ')'}) + } + } +} + +function fileExists (t, file, message) { + try { + fs.statSync(file) + t.pass(message) + } catch (ex) { + if (ex.code === 'ENOENT') { + t.fail(message, {found: null, wanted: 'exists', compare: 'fs.stat(' + file + ')'}) + } else { + t.fail(message, {found: ex, wanted: 'exists', compare: 'fs.stat(' + file + ')'}) + } + } +} + +function noFileExists (t, file, message) { + try { + fs.statSync(file) + t.fail(message, {found: 'exists', wanted: 'not exists', compare: 'fs.stat(' + file + ')'}) + } catch (ex) { + if (ex.code === 'ENOENT') { + t.pass(message) + } else { + t.fail(message, {found: ex, wanted: 'not exists', compare: 'fs.stat(' + file + ')'}) + } + } +} + +var server +var testdirContent = { + node_modules: Dir({}), + pkga: Dir({ + 'package.json': File({ + name: 'pkga', + version: '1.0.0' + }) + }), + pkgb: Dir({ + 'package.json': File({ + name: 'pkgb', + version: '1.0.0' + }) + }), + pkgc: Dir({ + 'package.json': File({ + name: 'pkgc', + version: '1.0.0' + }) + }), + pkgd: Dir({ + 'package.json': File({ + name: 'pkgd', + version: '1.0.0' + }) + }) +} + +var fixture +function setup () { + fixture = new Tacks(Dir({ + cache: Dir(), + global: Dir(), + tmp: Dir(), + testdir: Dir(testdirContent) + })) + cleanup() + fixture.create(basedir) +} + +function cleanup () { + fixture.remove(basedir) +} + +test('setup', function (t) { + process.nextTick(function () { + setup() + mr({port: common.port, throwOnUnmatched: true}, function (err, s) { + if (err) throw err + server = s + t.done() + }) + }) +}) + +var installOk = [] +var slashes = [ 'unix', 'win' ] +slashes.forEach(function (os) { + var slash = os === 'unix' + ? function (ss) { return ss.replace(/\\/g, '/') } + : function (ss) { return ss.replace(/\//g, '\\') } + installOk.push(os + '-file-abs-f') + testdirContent[os + '-file-abs-f'] = Dir({ + 'package.json': File({ + name: os + '-file-abs-f', + version: '1.0.0', + dependencies: { + pkga: 'file:' + slash(testdir + '/pkga') + } + }) + }) + installOk.push(os + '-file-abs-fff') + testdirContent[os + '-file-abs-fff'] = Dir({ + 'package.json': File({ + name: os + '-file-abs-fff', + version: '1.0.0', + dependencies: { + pkga: 'file://' + slash(testdir + '/pkga') + } + }) + }) + installOk.push(os + '-file-rel') + testdirContent[os + '-file-rel'] = Dir({ + 'package.json': File({ + name: os + '-file-rel', + version: '1.0.0', + dependencies: { + pkga: 'file:' + slash('../pkga') + } + }) + }) + installOk.push(os + '-file-rel-fffff') + testdirContent[os + '-file-rel-fffff'] = Dir({ + 'package.json': File({ + name: os + '-file-rel-fffff', + version: '1.0.0', + dependencies: { + pkga: 'file:' + slash('/////../pkga') + } + }) + }) +}) + +testdirContent['win-abs-drive-win'] = Dir({ + 'package.json': File({ + name: 'win-abs-drive-win', + version: '1.0.0', + dependencies: { + pkga: 'file:D:\\thing\\pkga' + } + }) +}) + +testdirContent['win-abs-drive-unix'] = Dir({ + 'package.json': File({ + name: 'win-abs-drive-unix', + version: '1.0.0', + dependencies: { + pkga: 'file://D:/thing/pkga' + } + }) +}) + +test('specifiers', function (t) { + t.plan(installOk.length + 2) + installOk.forEach(function (mod) { + t.test(mod, function (t) { + common.npm(['install', '--dry-run', '--json', 'file:' + mod], conf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + t.comment(stdout.trim()) + t.done() + }) + }) + }) + slashes.forEach(function (os) { + t.test('win-abs-drive-' + os, function (t) { + common.npm(['install', '--dry-run', '--json', 'file:win-abs-drive-' + os], confE, function (err, code, stdout, stderr) { + if (err) throw err + t.not(code, 0, 'command errored ok') + t.comment(stderr.trim()) + if (isWindows) { + t.test('verify failure of file-not-found or wrong drive letter on windows') + } else { + var result = JSON.parse(stdout) + t.is(result.error && result.error.code, 'EWINDOWSPATH', 'verify failure due to windows paths not supported on non-Windows') + } + + t.done() + }) + }) + }) +}) +testdirContent['mkdirp'] = Dir({ + 'package.json': File({ + name: 'mkdirp', + version: '9.9.9' + }) +}) +testdirContent['example'] = Dir({ + 'minimist': Dir({ + 'package.json': File({ + name: 'minimist', + version: '9.9.9' + }) + }) +}) +testdirContent['wordwrap'] = Dir({ +}) +testdirContent['prefer-pkg'] = Dir({ + 'package.json': File({ + name: 'perfer-pkg', + version: '1.0.0', + dependencies: { + 'mkdirp': 'latest' + } + }), + 'latest': Dir({ + 'package.json': File({ + name: 'mkdirp', + version: '9.9.9' + }) + }) +}) + +test('ambiguity', function (t) { + t.plan(5) + t.test('arg: looks like package name, is dir', function (t) { + common.npm(['install', 'mkdirp', '--dry-run', '--json'], conf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + const result = JSON.parse(stdout.trim()) + t.like(result, {added: [{name: 'mkdirp', version: '9.9.9'}]}, 'got local dir') + t.done() + }) + }) + t.test('arg: looks like package name, is package', function (t) { + common.npm(['install', 'wordwrap', '--dry-run', '--json'], conf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + const result = JSON.parse(stdout.trim()) + t.like(result, {added: [{name: 'wordwrap', version: '0.0.2'}]}, 'even with local dir w/o package.json, got global') + t.done() + }) + }) + t.test('arg: looks like github repo, is dir', function (t) { + common.npm(['install', 'example/minimist', '--dry-run', '--json'], conf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + const result = JSON.parse(stdout.trim()) + t.like(result, {added: [{name: 'minimist', version: '9.9.9'}]}, 'got local dir') + t.done() + }) + }) + t.test('package: looks like tag, has dir, use tag', function (t) { + common.npm(['install', '--dry-run', '--json'], {cwd: path.join(testdir, 'prefer-pkg'), env: conf.env}, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + const result = JSON.parse(stdout.trim()) + t.like(result, {added: [{name: 'mkdirp', version: '0.3.5'}]}, 'got local dir') + t.done() + }) + }) + + t.test('test ambiguity for github repos') +}) +testdirContent['existing-matches'] = Dir({ + 'package.json': File({ + name: 'existing-matches', + version: '1.0.0', + dependencies: { + 'pkga': 'file:../pkga', + 'pkgb': 'file:' + testdir + '/pkgb', + 'pkgc': 'file:../pkgc', + 'pkgd': 'file:' + testdir + '/pkgd' + } + }), + 'node_modules': Dir({ + 'pkga': Symlink('../../pkga'), + 'pkgd': Symlink('../../pkgd') + }) +}) + +test('existing install matches', function (t) { + t.plan(1) + // have to make these by hand because tacks doesn't support absolute paths in symlinks + fs.symlinkSync(testdir + '/pkgb', testdir + '/existing-matches/node_modules/pkgb', 'junction') + fs.symlinkSync(testdir + '/pkgc', testdir + '/existing-matches/node_modules/pkgc', 'junction') + t.test('relative symlink counts as match of relative symlink in package.json', function (t) { + common.npm(['ls', '--json'], {cwd: path.join(testdir, 'existing-matches'), env: conf.env}, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + if (stdout) t.comment(stdout.trim()) + t.test('specifically test that output is valid') + // relative symlink counts as match of relative symlink in package.json (pkga) + // relative symlink counts as match of abs symlink in package.json (pkgc) + // abs symlink counts as match of relative symlink in package.json (pkgb) + // abs symlink counts as match of abs symlink in package.json (pkgd) + t.done() + }) + }) +}) +var ibdir = testdir + '/install-behavior' +testdirContent['ib-out'] = Dir({ + 'package.json': File({ + name: 'ib-out', + version: '1.0.0', + dependencies: { + 'minimist': '*' + } + }) +}) + +testdirContent['install-behavior'] = Dir({ + 'package.json': File({ + name: 'install-behavior', + version: '1.0.0' + }), + 'ib-in': Dir({ + 'package.json': File({ + name: 'ib-in', + version: '1.0.0', + dependencies: { + 'mkdirp': '*' + } + }) + }), + 'noext': File(new Buffer( + '1f8b08000000000000032b484cce4e4c4fd52f80d07a59c5f9790c540606' + + '06066626260a20dadccc144c1b1841f86000923334363037343536343732' + + '633000728c0c80f2d4760836505a5c925804740aa5e640bca200a78708a8' + + '56ca4bcc4d55b252cacb4fad2851d251502a4b2d2acecccf030a19ea19e8' + + '1928d5720db41b47c1281805a36014501f00005012007200080000', + 'hex' + )), + 'tarball-1.0.0.tgz': File(new Buffer( + '1f8b08000000000000032b484cce4e4c4fd52f80d07a59c5f9790c540606' + + '06066626260a20dadccc144c1b1841f8606062a6c060686c606e686a6c68' + + '666ec26000e480e5a9ed106ca0b4b824b108e8144acd817845014e0f1150' + + 'ad9497989baa64a5040c85a4c49c1c251d05a5b2d4a2e2ccfc3ca0a0a19e' + + '819e81522dd740bb72148c8251300a4601b50100473dd15800080000', + 'hex' + )), + 'not-module': Dir({}), + 'test-preinstall': Dir({ + 'package.json': File({ + name: 'test-preinstall', + version: '1.0.0', + scripts: { + 'preinstall': 'node -p "\'CWD:\' + process.cwd()"' + } + }) + }) +}) + +test('install behavior', function (t) { + var ibconf = {cwd: ibdir, env: conf.env.extend({npm_config_loglevel: 'silent'}), stdio: conf.stdio} + t.plan(7) + t.test('transitive deps for in-larger-module cases', function (t) { + common.npm(['install', 'file:ib-in'], ibconf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + if (stdout) t.comment(stdout.trim()) + fileExists(t, ibdir + '/node_modules/mkdirp', 'transitive dep flattened') + isSymlink(t, ibdir + '/node_modules/ib-in', 'dep is symlink') + noFileExists(t, ibdir + '/ib-in/node_modules/mkdirp', 'transitive dep not nested') + t.done() + }) + }) + t.test('transitive deps for out-of-larger module cases', function (t) { + common.npm(['install', 'file:../ib-out'], ibconf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + if (stdout) t.comment(stdout.trim()) + noFileExists(t, ibdir + '/node_modules/minimist', 'transitive dep not flattened') + fileExists(t, testdir + '/ib-out/node_modules/minimist', 'transitive dep nested') + t.done() + }) + }) + t.test('transitive deps for out-of-larger module cases w/ --preserve-symlinks', function (t) { + rimraf.sync(ibdir + '/node_modules') + rimraf.sync(testdir + '/ib-out/node_modules') + var env = conf.env.extend({NODE_PRESERVE_SYMLINKS: '1'}) + common.npm(['install', 'file:../ib-out'], {cwd: ibdir, env: env}, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + if (stdout) t.comment(stdout.trim()) + fileExists(t, ibdir + '/node_modules/minimist', 'transitive dep flattened') + noFileExists(t, testdir + '/ib-out/node_modules/minimist', 'transitive dep not nested') + t.done() + }) + }) + t.test('tar/tgz/tar.gz should install a tarball', function (t) { + common.npm(['install', 'file:tarball-1.0.0.tgz'], ibconf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + if (stdout) t.comment(stdout.trim()) + fileExists(t, ibdir + '/node_modules/tarball') + t.done() + }) + }) + t.test('non tar/tgz/tar.gz files should give good error message', function (t) { + common.npm(['install', 'file:noext', '--json'], ibconf, function (err, code, stdout) { + if (err) throw err + t.not(code, 0, 'do not install files w/o extensions') + noFileExists(t, ibdir + '/node_modules/noext') + var result = JSON.parse(stdout) + t.like(result, {error: {code: 'ENOLOCAL'}}, 'new type of error') + t.done() + }) + }) + t.test('directories without package.json should give good error message', function (t) { + common.npm(['install', 'file:not-module', '--json'], ibconf, function (err, code, stdout) { + if (err) throw err + t.not(code, 0, 'error on dir w/o module') + var result = JSON.parse(stdout) + t.like(result, {error: {code: 'ENOLOCAL'}}, 'new type of error') + t.done() + }) + }) + t.test('verify preinstall step runs after finalize, such that cwd is as expected', function (t) { + common.npm(['install', 'file:test-preinstall'], ibconf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + isSymlink(t, `${ibdir}/node_modules/test-preinstall`, 'installed as symlink') + t.notLike(stdout, /CWD:.*[.]staging/, 'cwd should not be in staging') + + t.test('verify that env is as expected') + t.done() + }) + }) +}) +var sbdir = testdir + '/save-behavior' +testdirContent['save-behavior'] = Dir({ + 'package.json': File({ + name: 'save-behavior', + version: '1.0.0' + }), + 'npm-shrinkwrap.json': File({ + name: 'save-behavior', + version: '1.0.0', + dependencies: {} + }), + 'transitive': Dir({ + 'package.json': File({ + name: 'transitive', + version: '1.0.0', + dependencies: { + 'pkgc': 'file:../../pkgc' + } + }) + }) +}) +testdirContent['sb-transitive'] = Dir({ + 'package.json': File({ + name: 'sb-transitive', + version: '1.0.0', + dependencies: { + 'sbta': 'file:sbta' + } + }), + 'sbta': Dir({ + 'package.json': File({ + name: 'sbta', + version: '1.0.0' + }) + }) +}) +var sbdirp = testdir + '/save-behavior-pre' +testdirContent['save-behavior-pre'] = Dir({ + 'package.json': File({ + name: 'save-behavior', + version: '1.0.0' + }), + 'npm-shrinkwrap.json': File({ + name: 'save-behavior', + version: '1.0.0', + dependencies: {} + }) +}) +testdirContent['sb-transitive-preserve'] = Dir({ + 'package.json': File({ + name: 'sb-transitive-preserve', + version: '1.0.0', + dependencies: { + 'sbtb': 'file:sbtb' + } + }), + 'sbtb': Dir({ + 'package.json': File({ + name: 'sbtb', + version: '1.0.0' + }) + }) +}) +test('save behavior', function (t) { + t.plan(6) + var sbconf = {cwd: sbdir, env: conf.env, stdio: conf.stdio} + t.test('to package.json and npm-shrinkwrap.json w/ abs', function (t) { + common.npm(['install', '--save', 'file:' + testdir + '/pkga'], sbconf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + var pjson = readJson(sbdir + '/package.json') + var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json') + t.is(pjson.dependencies.pkga, 'file:../pkga', 'package.json') + var sdep = shrinkwrap.dependencies + t.like(sdep, {pkga: {version: 'file:../pkga', resolved: null}}, 'npm-shrinkwrap.json') + t.done() + }) + }) + t.test('to package.json and npm-shrinkwrap.json w/ drive abs') + t.test('to package.json and npm-shrinkwrap.json w/ rel', function (t) { + common.npm(['install', '--save', 'file:../pkgb'], sbconf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + var pjson = readJson(sbdir + '/package.json') + var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json') + t.is(pjson.dependencies.pkgb, 'file:../pkgb', 'package.json') + var sdep = shrinkwrap.dependencies + t.like(sdep, {pkgb: {version: 'file:../pkgb', resolved: null}}, 'npm-shrinkwrap.json') + t.done() + }) + }) + t.test('internal transitive dependencies of shrinkwraps', function (t) { + common.npm(['install', '--save', 'file:transitive'], sbconf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + var pjson = readJson(sbdir + '/package.json') + var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json') + t.is(pjson.dependencies.transitive, 'file:transitive', 'package.json') + var sdep = shrinkwrap.dependencies.transitive || {} + var tdep = shrinkwrap.dependencies.pkgc + t.is(sdep.version, 'file:transitive', 'npm-shrinkwrap.json direct dep') + t.is(tdep.version, 'file:../pkgc', 'npm-shrinkwrap.json transitive dep') + t.done() + }) + }) + t.test('external transitive dependencies of shrinkwraps', function (t) { + common.npm(['install', '--save', 'file:../sb-transitive'], sbconf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + var pjson = readJson(sbdir + '/package.json') + var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json') + var deps = pjson.dependencies || {} + t.is(deps['sb-transitive'], 'file:../sb-transitive', 'package.json') + var sdep = shrinkwrap.dependencies['sb-transitive'] || {} + var tdep = sdep.dependencies.sbta + t.like(tdep, {bundled: true, version: 'file:../sb-transitive/sbta'}, 'npm-shrinkwrap.json transitive dep') + t.like(sdep, {bundled: null, version: 'file:../sb-transitive'}, 'npm-shrinkwrap.json direct dep') + t.done() + }) + }) + t.test('external transitive dependencies of shrinkwraps > preserve symlinks', function (t) { + var preserveEnv = conf.env.extend({NODE_PRESERVE_SYMLINKS: '1'}) + var preserveConf = {cwd: sbdirp, env: preserveEnv, stdio: conf.stdio} + common.npm(['install', '--save', 'file:../sb-transitive-preserve'], preserveConf, function (err, code, stdout) { + if (err) throw err + t.is(code, 0, 'command ran ok') + var pjson = readJson(sbdirp + '/package.json') + var shrinkwrap = readJson(sbdirp + '/npm-shrinkwrap.json') + t.is(pjson.dependencies['sb-transitive-preserve'], 'file:../sb-transitive-preserve', 'package.json') + var sdep = shrinkwrap.dependencies['sb-transitive-preserve'] || {} + var tdep = shrinkwrap.dependencies.sbtb + t.like(sdep, {bundled: null, version: 'file:../sb-transitive-preserve'}, 'npm-shrinkwrap.json direct dep') + t.like(tdep, {bundled: null, version: 'file:../sb-transitive-preserve/sbtb'}, 'npm-shrinkwrap.json transitive dep') + t.done() + }) + }) +}) + +test('removal', function (t) { + t.plan(3) + t.test('should remove the symlink') + t.test('should not remove the transitive deps if it was not a `link:` type specifier.') + t.test("should not remove transitive deps if it's outside the package and --preserver-symlinks isn't set") +}) + +test('misc', function (t) { + t.plan(3) + t.test('listing: should look right, not include version') + t.test('outdated: show LOCAL for wanted / latest') + t.test('update: if specifier exists, do nothing. otherwise as if `npm install`.') +}) + +test('cleanup', function (t) { + server.close() + cleanup() + t.done() +}) |