|
|
|
'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')
|
|
|
|
})
|