You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

387 lines
15 KiB

'use strict'
var test = require('tap').test
var requireInject = require('require-inject')
var path = require('path')
function error (code) {
var er = new Error()
er.code = code
return er
}
function platformPath (unixPath) {
if (unixPath[0] === '/') {
return path.resolve(unixPath)
} else {
return path.join.apply(path, unixPath.split('/'))
}
}
function makeObjUsePlatformPaths (obj) {
if (typeof obj === 'string') {
return platformPath(obj)
} else if (obj == null || typeof obj !== 'object') {
return obj
} else {
Object.keys(obj).forEach(function (key) {
var newKey = platformPath(key)
obj[key] = makeObjUsePlatformPaths(obj[key])
if (newKey !== key) {
obj[newKey] = obj[key]
delete obj[key]
}
})
}
return obj
}
function makeArgsUsePlatformPaths (fn) {
return function () {
var args = Array.prototype.slice.call(arguments)
return fn.apply(null, makeObjUsePlatformPaths(args))
}
}
function pathIs (t, arg1, arg2, msg) {
t.is(arg1, makeObjUsePlatformPaths(arg2), msg)
}
function pathIsDeeply (t, arg1, arg2, msg) {
t.isDeeply(arg1, makeObjUsePlatformPaths(arg2), msg)
}
function mockWith (fixture) {
makeObjUsePlatformPaths(fixture)
return {
'../../lib/npm.js': {},
'graceful-fs': {
lstat: function (path, cb) {
path = platformPath(path)
var entry = fixture[path]
if (!entry) return cb(error('ENOENT'))
cb(null, {
isDirectory: function () { return entry.type === 'directory' },
isSymbolicLink: function () { return entry.type === 'symlink' },
isFile: function () { return entry.type === 'file' || entry.type === 'cmdshim' || entry.type === 'error' }
})
},
readlink: function (path, cb) {
path = platformPath(path)
var entry = fixture[path]
if (!entry) return cb(error('ENOENT'))
if (entry.type !== 'symlink') return cb(error('EINVAL'))
cb(null, entry.dest)
}
},
'read-cmd-shim': function (path, cb) {
path = platformPath(path)
var entry = fixture[path]
if (!entry) return cb(error('ENOENT'))
if (entry.type === 'directory') return cb(error('EISDIR'))
if (entry.type === 'error') return cb(error(entry.code))
if (entry.type !== 'cmdshim') return cb(error('ENOTASHIM'))
cb(null, entry.dest)
}
}
}
test('readLinkOrShim', function (t) {
t.plan(10)
var mocks = mockWith({
'/path/to/directory': { type: 'directory' },
'/path/to/link': { type: 'symlink', dest: '../to/file' },
'/path/to/file': { type: 'file' },
'/path/to/cmdshim': { type: 'cmdshim', dest: '../to/file' },
'/path/to/invalid': { type: 'error', code: 'EINVAL' }
})
var gentlyRm = requireInject('../../lib/utils/gently-rm.js', mocks)
var readLinkOrShim = makeArgsUsePlatformPaths(gentlyRm._readLinkOrShim)
readLinkOrShim('/path/to/nowhere', function (er, path) {
t.is(er && er.code, 'ENOENT', 'missing files are errors')
})
readLinkOrShim('/path/to/invalid', function (er, path) {
t.is(er && er.code, 'EINVAL', 'other errors pass through too')
})
readLinkOrShim('/path/to/directory', function (er, path) {
t.ifError(er, "reading dirs isn't an error")
pathIs(t, path, null, 'reading non links/cmdshims gives us null')
})
readLinkOrShim('/path/to/file', function (er, path) {
t.ifError(er, "reading non-cmdshim files isn't an error")
pathIs(t, path, null, 'reading non links/cmdshims gives us null')
})
readLinkOrShim('/path/to/link', function (er, path) {
t.ifError(er, "reading links isn't an error")
pathIs(t, path, '../to/file', 'reading links works')
})
readLinkOrShim('/path/to/cmdshim', function (er, path) {
t.ifError(er, "reading cmdshims isn't an error")
pathIs(t, path, '../to/file', 'reading cmdshims works')
})
t.done()
})
test('resolveSymlink', function (t) {
t.plan(9)
var mocks = mockWith({
'/path/to/directory': { type: 'directory' },
'/path/to/link': { type: 'symlink', dest: '../to/file' },
'/path/to/file': { type: 'file' },
'/path/to/cmdshim': { type: 'cmdshim', dest: '../to/file' }
})
var gentlyRm = requireInject('../../lib/utils/gently-rm.js', mocks)
var resolveSymlink = makeArgsUsePlatformPaths(gentlyRm._resolveSymlink)
resolveSymlink('/path/to/nowhere', function (er, path) {
t.is(er && er.code, 'ENOENT', 'missing files are errors')
})
// these aren't symlinks so we get back what we passed in
resolveSymlink('/path/to/directory', function (er, path) {
t.ifError(er, "reading dirs isn't an error")
pathIs(t, path, '/path/to/directory', 'reading non links/cmdshims gives us path we passed in')
})
resolveSymlink('/path/to/file', function (er, path) {
t.ifError(er, "reading non-cmdshim files isn't an error")
pathIs(t, path, '/path/to/file', 'reading non links/cmdshims gives us the path we passed in')
})
// these are symlinks so the resolved version is platform specific
resolveSymlink('/path/to/link', function (er, path) {
t.ifError(er, "reading links isn't an error")
pathIs(t, path, '/path/to/file', 'reading links works')
})
resolveSymlink('/path/to/cmdshim', function (er, path) {
t.ifError(er, "reading cmdshims isn't an error")
pathIs(t, path, '/path/to/file', 'reading cmdshims works')
})
t.done()
})
test('readAllLinks', function (t) {
t.plan(16)
var mocks = mockWith({
'/path/to/directory': { type: 'directory' },
'/path/to/link': { type: 'symlink', dest: '../to/file' },
'/path/to/file': { type: 'file' },
'/path/to/cmdshim': { type: 'cmdshim', dest: '../to/file' },
'/path/to/linktolink': { type: 'symlink', dest: 'link' },
'/path/to/linktolink^2': { type: 'symlink', dest: 'linktolink' },
'/path/to/linktocmdshim': { type: 'symlink', dest: 'cmdshim' },
'/path/to/linktobad': { type: 'symlink', dest: '/does/not/exist' }
})
var gentlyRm = requireInject('../../lib/utils/gently-rm.js', mocks)
var readAllLinks = makeArgsUsePlatformPaths(gentlyRm._readAllLinks)
readAllLinks('/path/to/nowhere', function (er, path) {
t.is(er && er.code, 'ENOENT', 'missing files are errors')
})
readAllLinks('/path/to/directory', function (er, path) {
t.ifError(er, "reading dirs isn't an error")
pathIsDeeply(t, path, ['/path/to/directory'], 'reading non links/cmdshims gives us path we passed in')
})
readAllLinks('/path/to/file', function (er, path) {
t.ifError(er, "reading non-cmdshim files isn't an error")
pathIsDeeply(t, path, ['/path/to/file'], 'reading non links/cmdshims gives us the path we passed in')
})
readAllLinks('/path/to/linktobad', function (er, path) {
t.is(er && er.code, 'ENOENT', 'links to missing files are errors')
})
readAllLinks('/path/to/link', function (er, results) {
t.ifError(er, "reading links isn't an error")
pathIsDeeply(t, results, ['/path/to/link', '/path/to/file'], 'reading links works')
})
readAllLinks('/path/to/cmdshim', function (er, path) {
t.ifError(er, "reading cmdshims isn't an error")
pathIsDeeply(t, path, ['/path/to/cmdshim', '/path/to/file'], 'reading cmdshims works')
})
readAllLinks('/path/to/linktolink', function (er, path) {
t.ifError(er, "reading link to link isn't an error")
pathIsDeeply(t, path, ['/path/to/linktolink', '/path/to/link', '/path/to/file'], 'reading link to link works')
})
readAllLinks('/path/to/linktolink^2', function (er, path) {
t.ifError(er, "reading link to link to link isn't an error")
pathIsDeeply(t, path, ['/path/to/linktolink^2', '/path/to/linktolink', '/path/to/link', '/path/to/file'], 'reading link to link to link works')
})
readAllLinks('/path/to/linktocmdshim', function (er, path) {
t.ifError(er, "reading link to cmdshim isn't an error")
pathIsDeeply(t, path, ['/path/to/linktocmdshim', '/path/to/cmdshim', '/path/to/file'], 'reading link to cmdshim works')
})
t.done()
})
test('areAnyInsideAny', function (t) {
var gentlyRm = requireInject('../../lib/utils/gently-rm.js', mockWith({}))
var areAnyInsideAny = makeArgsUsePlatformPaths(gentlyRm._areAnyInsideAny)
var noneOneToOne = areAnyInsideAny(['/abc'], ['/xyz'])
t.is(noneOneToOne, false, 'none inside: one to one')
var noneOneToMany = areAnyInsideAny(['/abc'], ['/rst', '/uvw', '/xyz'])
t.is(noneOneToMany, false, 'none inside: one to many')
var noneManyToOne = areAnyInsideAny(['/abc', '/def', '/ghi'], ['/xyz'])
t.is(noneManyToOne, false, 'none inside: many to one')
var noneManyToMany = areAnyInsideAny(['/abc', '/def', '/ghi'], ['/rst', '/uvw', '/xyz'])
t.is(noneManyToMany, false, 'none inside: many to many')
var oneToOne = areAnyInsideAny(['/one/toOne'], ['/one'])
pathIsDeeply(t, oneToOne, {target: '/one/toOne', path: '/one'}, 'first: one to one')
var firstOneToMany = areAnyInsideAny(['/abc/def'], ['/abc', '/def', '/ghi'])
pathIsDeeply(t, firstOneToMany, {target: '/abc/def', path: '/abc'}, 'first: one to many')
var secondOneToMany = areAnyInsideAny(['/def/ghi'], ['/abc', '/def', '/ghi'])
pathIsDeeply(t, secondOneToMany, {target: '/def/ghi', path: '/def'}, 'second: one to many')
var lastOneToMany = areAnyInsideAny(['/ghi/jkl'], ['/abc', '/def', '/ghi'])
pathIsDeeply(t, lastOneToMany, {target: '/ghi/jkl', path: '/ghi'}, 'last: one to many')
var firstManyToOne = areAnyInsideAny(['/abc/def', '/uvw/def', '/xyz/def'], ['/abc'])
pathIsDeeply(t, firstManyToOne, {target: '/abc/def', path: '/abc'}, 'first: many to one')
var secondManyToOne = areAnyInsideAny(['/abc/def', '/uvw/def', '/xyz/def'], ['/uvw'])
pathIsDeeply(t, secondManyToOne, {target: '/uvw/def', path: '/uvw'}, 'second: many to one')
var lastManyToOne = areAnyInsideAny(['/abc/def', '/uvw/def', '/xyz/def'], ['/xyz'])
pathIsDeeply(t, lastManyToOne, {target: '/xyz/def', path: '/xyz'}, 'last: many to one')
var firstToFirst = areAnyInsideAny(['/abc/def', '/uvw/def', '/xyz/def'], ['/abc', '/uvw', '/xyz'])
pathIsDeeply(t, firstToFirst, {target: '/abc/def', path: '/abc'}, 'first to first: many to many')
var firstToSecond = areAnyInsideAny(['/abc/def', '/uvw/def', '/xyz/def'], ['/nope', '/abc', '/xyz'])
pathIsDeeply(t, firstToSecond, {target: '/abc/def', path: '/abc'}, 'first to second: many to many')
var firstToLast = areAnyInsideAny(['/abc/def', '/uvw/def', '/xyz/def'], ['/nope', '/nooo', '/abc'])
pathIsDeeply(t, firstToLast, {target: '/abc/def', path: '/abc'}, 'first to last: many to many')
var secondToFirst = areAnyInsideAny(['/!!!', '/abc/def', '/xyz/def'], ['/abc', '/uvw', '/xyz'])
pathIsDeeply(t, secondToFirst, {target: '/abc/def', path: '/abc'}, 'second to first: many to many')
var secondToSecond = areAnyInsideAny(['/!!!', '/abc/def', '/xyz/def'], ['/nope', '/abc', '/xyz'])
pathIsDeeply(t, secondToSecond, {target: '/abc/def', path: '/abc'}, 'second to second: many to many')
var secondToLast = areAnyInsideAny(['/!!!', '/abc/def', '/uvw/def'], ['/nope', '/nooo', '/abc'])
pathIsDeeply(t, secondToLast, {target: '/abc/def', path: '/abc'}, 'second to last: many to many')
var lastToFirst = areAnyInsideAny(['/!!!', '/???', '/abc/def'], ['/abc', '/uvw', '/xyz'])
pathIsDeeply(t, lastToFirst, {target: '/abc/def', path: '/abc'}, 'last to first: many to many')
var lastToSecond = areAnyInsideAny(['/!!!', '/???', '/abc/def'], ['/nope', '/abc', '/xyz'])
pathIsDeeply(t, lastToSecond, {target: '/abc/def', path: '/abc'}, 'last to second: many to many')
var lastToLast = areAnyInsideAny(['/!!!', '/???', '/abc/def'], ['/nope', '/nooo', '/abc'])
pathIsDeeply(t, lastToLast, {target: '/abc/def', path: '/abc'}, 'last to last: many to many')
t.done()
})
test('isEverInside', function (t) {
t.plan(15)
var mocks = mockWith({
'/path/other/link': { type: 'symlink', dest: '../to/file' },
'/path/to/file': { type: 'file' },
'/path/to': { type: 'directory' },
'/linkpath': { type: 'symlink', dest: '../path/to' },
'/path/to/invalid': { type: 'error', code: 'EINVAL' }
})
var gentlyRm = requireInject('../../lib/utils/gently-rm.js', mocks)
var isEverInside = makeArgsUsePlatformPaths(gentlyRm._isEverInside)
isEverInside('/path/to/file', ['/path/to', '/path/to/invalid'], function (er, inside) {
t.ifError(er)
pathIsDeeply(t, inside, {target: '/path/to/file', path: '/path/to'}, 'bad paths are ignored if something matches')
})
isEverInside('/path/to/invalid', ['/path/to/invalid'], function (er, inside) {
t.is(er && er.code, 'EINVAL', 'errors bubble out')
})
isEverInside('/path/to/file', ['/ten'], function (er, inside) {
t.ifError(er)
t.is(inside, false, 'not inside')
})
isEverInside('/path/to/nowhere', ['/ten'], function (er, inside) {
t.ifError(er)
t.is(inside, false, 'missing target')
})
isEverInside('/path/to/file', ['/path/to'], function (er, inside) {
t.ifError(er)
pathIsDeeply(t, inside, {target: '/path/to/file', path: '/path/to'}, 'plain file in plain path')
})
isEverInside('/path/other/link', ['/path/to'], function (er, inside) {
t.ifError(er)
pathIsDeeply(t, inside, {target: '/path/to/file', path: '/path/to'}, 'link in plain path')
})
isEverInside('/path/to/file', ['/linkpath'], function (er, inside) {
t.ifError(er)
pathIsDeeply(t, inside, {target: '/path/to/file', path: '/path/to'}, 'plain file in link path')
})
isEverInside('/path/other/link', ['/linkpath'], function (er, inside) {
t.ifError(er)
pathIsDeeply(t, inside, {target: '/path/to/file', path: '/path/to'}, 'link in link path')
})
t.done()
})
test('isSafeToRm', function (t) {
var gentlyRm = requireInject('../../lib/utils/gently-rm.js', mockWith({}))
var isSafeToRm = makeArgsUsePlatformPaths(gentlyRm._isSafeToRm)
t.plan(12)
function testIsSafeToRm (t, parent, target, shouldPath, shouldBase, msg) {
isSafeToRm(parent, target, function (er, path, base) {
t.ifError(er, msg + ' no error')
pathIs(t, path, shouldPath, msg + ' path')
pathIs(t, base, shouldBase, msg + ' base')
})
}
function testNotIsSafeToRm (t, parent, target, msg) {
isSafeToRm(parent, target, function (er) {
t.is(er && er.code, 'EEXIST', msg + ' error')
})
}
var unmanagedParent = {path: '/foo', managed: false}
var managedParent = {path: '/foo', managed: true}
var targetInParent = {
path: '/foo/bar/baz',
inParent: {
target: '/foo/bar/baz',
path: '/foo'
}
}
var targetLinkInParent = {
path: '/foo/bar/baz',
inParent: {
target: '/other/area/baz',
path: '/other/area'
}
}
var targetManagedLinkNotInParent = {
path: '/foo/bar/baz',
managed: true,
inParent: false,
symlink: '/foo/bar/bark'
}
var targetUnmanagedLink = {
path: '/not/managed/baz',
managed: false,
inParent: false,
symlink: '/not/managed/foo'
}
var targetUnmanagedFile = {
path: '/not/managed/baz',
managed: false,
inParent: false,
symlink: false
}
testNotIsSafeToRm(t, unmanagedParent, targetInParent, 'unmanaged parent')
testIsSafeToRm(t, managedParent, targetInParent, '/foo/bar/baz', '/foo', 'path is in parent')
testIsSafeToRm(t, managedParent, targetLinkInParent, '/foo/bar/baz', '/foo/bar', 'path links to parent')
testIsSafeToRm(t, managedParent, targetManagedLinkNotInParent, undefined, undefined, 'managed but not owned by package')
testNotIsSafeToRm(t, managedParent, targetUnmanagedLink, 'unmanaged link')
testNotIsSafeToRm(t, managedParent, targetUnmanagedFile, 'unmanaged file')
})