1'use strict' 2/* eslint-disable camelcase */ 3 4const configCommon = require('./common-config.js') 5var fs = require('graceful-fs') 6var readCmdShim = require('read-cmd-shim') 7var isWindows = require('../lib/utils/is-windows.js') 8var Bluebird = require('bluebird') 9 10if (isWindows) { 11 var PATH = process.env.PATH ? 'PATH' : 'Path' 12 process.env[PATH] += ';C:\\Program Files\\Git\\mingw64\\libexec\\git-core' 13} 14 15// remove any git envs so that we don't mess with the main repo 16// when running git subprocesses in tests 17Object.keys(process.env).filter(k => /^GIT/.test(k)).forEach( 18 k => delete process.env[k] 19) 20 21// cheesy hackaround for test deps (read: nock) that rely on setImmediate 22if (!global.setImmediate || !require('timers').setImmediate) { 23 require('timers').setImmediate = global.setImmediate = function () { 24 var args = [arguments[0], 0].concat([].slice.call(arguments, 1)) 25 setTimeout.apply(this, args) 26 } 27} 28 29var spawn = require('child_process').spawn 30const spawnSync = require('child_process').spawnSync 31var path = require('path') 32 33// space these out to help prevent collisions 34const testId = 3 * (+process.env.TAP_CHILD_ID || 0) 35 36// provide a working dir unique to each test 37const main = require.main.filename 38const testName = path.basename(main, '.js') 39exports.pkg = path.resolve(path.dirname(main), testName) 40var commonCache = path.resolve(__dirname, 'npm_cache_' + testName) 41exports.cache = commonCache 42 43const mkdirp = require('mkdirp') 44const rimraf = require('rimraf') 45rimraf.sync(exports.pkg) 46rimraf.sync(commonCache) 47mkdirp.sync(exports.pkg) 48mkdirp.sync(commonCache) 49// if we're in sudo mode, make sure that the cache is not root-owned 50const isRoot = process.getuid && process.getuid() === 0 51const isSudo = isRoot && process.env.SUDO_UID && process.env.SUDO_GID 52if (isSudo) { 53 const sudoUid = +process.env.SUDO_UID 54 const sudoGid = +process.env.SUDO_GID 55 fs.chownSync(commonCache, sudoUid, sudoGid) 56} 57 58const returnCwd = path.dirname(__dirname) 59const find = require('which').sync('find') 60require('tap').teardown(() => { 61 // work around windows folder locking 62 process.chdir(returnCwd) 63 process.on('exit', () => { 64 try { 65 if (isSudo) { 66 // running tests as sudo. ensure we didn't leave any root-owned 67 // files in the cache by mistake. 68 const args = [ commonCache, '-uid', '0' ] 69 const found = spawnSync(find, args) 70 const output = found && found.stdout && found.stdout.toString() 71 if (output.length) { 72 const er = new Error('Root-owned files left in cache!') 73 er.testName = main 74 er.files = output.trim().split('\n') 75 throw er 76 } 77 } 78 if (!process.env.NO_TEST_CLEANUP) { 79 rimraf.sync(exports.pkg) 80 rimraf.sync(commonCache) 81 } 82 } catch (e) { 83 if (process.platform !== 'win32') { 84 throw e 85 } 86 } 87 }) 88}) 89 90var port = exports.port = 15443 + testId 91exports.registry = 'http://localhost:' + port 92 93exports.altPort = 7331 + testId 94 95exports.gitPort = 4321 + testId 96 97var fakeRegistry = require('./fake-registry.js') 98exports.fakeRegistry = fakeRegistry 99 100const ourenv = {} 101ourenv.npm_config_loglevel = 'error' 102ourenv.npm_config_progress = 'false' 103ourenv.npm_config_metrics = 'false' 104ourenv.npm_config_audit = 'false' 105 106ourenv.npm_config_unsafe_perm = 'true' 107ourenv.npm_config_cache = commonCache 108ourenv.npm_config_userconfig = exports.npm_config_userconfig = configCommon.userconfig 109ourenv.npm_config_globalconfig = exports.npm_config_globalconfig = configCommon.globalconfig 110ourenv.npm_config_global_style = 'false' 111ourenv.npm_config_legacy_bundling = 'false' 112ourenv.npm_config_fetch_retries = '0' 113ourenv.npm_config_update_notifier = 'false' 114ourenv.random_env_var = 'foo' 115// suppress warnings about using a prerelease version of node 116ourenv.npm_config_node_version = process.version.replace(/-.*$/, '') 117for (let key of Object.keys(ourenv)) process.env[key] = ourenv[key] 118 119var bin = exports.bin = require.resolve('../bin/npm-cli.js') 120 121var chain = require('slide').chain 122var once = require('once') 123 124var nodeBin = exports.nodeBin = process.env.npm_node_execpath || process.env.NODE || process.execPath 125 126exports.npm = function (cmd, opts, cb) { 127 if (!cb) { 128 var prom = new Bluebird((resolve, reject) => { 129 cb = function (err, code, stdout, stderr) { 130 if (err) return reject(err) 131 return resolve([code, stdout, stderr]) 132 } 133 }) 134 } 135 cb = once(cb) 136 cmd = [bin].concat(cmd) 137 opts = Object.assign({}, opts || {}) 138 139 opts.env = opts.env || process.env 140 if (opts.env._storage) opts.env = Object.assign({}, opts.env._storage) 141 if (!opts.env.npm_config_cache) { 142 opts.env.npm_config_cache = commonCache 143 } 144 if (!opts.env.npm_config_unsafe_perm) { 145 opts.env.npm_config_unsafe_perm = 'true' 146 } 147 if (!opts.env.npm_config_send_metrics) { 148 opts.env.npm_config_send_metrics = 'false' 149 } 150 if (!opts.env.npm_config_audit) { 151 opts.env.npm_config_audit = 'false' 152 } 153 154 nodeBin = opts.nodeExecPath || nodeBin 155 156 var stdout = '' 157 var stderr = '' 158 var child = spawn(nodeBin, cmd, opts) 159 160 if (child.stderr) { 161 child.stderr.on('data', function (chunk) { 162 stderr += chunk 163 }) 164 } 165 166 if (child.stdout) { 167 child.stdout.on('data', function (chunk) { 168 stdout += chunk 169 }) 170 } 171 172 child.on('error', cb) 173 174 child.on('close', function (code) { 175 cb(null, code, stdout, stderr) 176 }) 177 return prom || child 178} 179 180exports.makeGitRepo = function (params, cb) { 181 // git must be called after npm.load because it uses config 182 var git = require('../lib/utils/git.js') 183 184 var root = params.path || process.cwd() 185 var user = params.user || 'PhantomFaker' 186 var email = params.email || 'nope@not.real' 187 var added = params.added || ['package.json'] 188 var message = params.message || 'stub repo' 189 190 var opts = { cwd: root, env: { PATH: process.env.PATH || process.env.Path } } 191 var commands = [ 192 git.chainableExec(['init'], opts), 193 git.chainableExec(['config', 'user.name', user], opts), 194 git.chainableExec(['config', 'user.email', email], opts), 195 // don't time out tests waiting for a gpg passphrase or 2fa 196 git.chainableExec(['config', 'commit.gpgSign', 'false'], opts), 197 git.chainableExec(['config', 'tag.gpgSign', 'false'], opts), 198 git.chainableExec(['config', 'tag.forceSignAnnotated', 'false'], opts), 199 git.chainableExec(['add'].concat(added), opts), 200 git.chainableExec(['commit', '-m', message], opts) 201 ] 202 203 if (Array.isArray(params.commands)) { 204 commands = commands.concat(params.commands) 205 } 206 207 chain(commands, cb) 208} 209 210exports.readBinLink = function (path) { 211 if (isWindows) { 212 return readCmdShim.sync(path) 213 } else { 214 return fs.readlinkSync(path) 215 } 216} 217 218exports.skipIfWindows = function (why) { 219 if (!isWindows) return 220 if (!why) why = 'this test not available on windows' 221 require('tap').plan(0, why) 222 process.exit(0) 223} 224 225exports.pendIfWindows = function (why) { 226 if (!isWindows) return 227 if (!why) why = 'this test is pending further changes on windows' 228 require('tap').fail(' ', { todo: why, diagnostic: false }) 229 process.exit(0) 230} 231 232exports.withServer = cb => { 233 return fakeRegistry.compat().tap(cb).then(server => server.close()) 234} 235 236exports.newEnv = function () { 237 return new Environment(process.env) 238} 239 240exports.emptyEnv = function () { 241 const filtered = {} 242 for (let key of Object.keys(process.env)) { 243 if (!/^npm_/.test(key)) filtered[key] = process.env[key] 244 } 245 for (let key of Object.keys(ourenv)) { 246 filtered[key] = ourenv[key] 247 } 248 return new Environment(filtered) 249} 250 251function Environment (env) { 252 if (env instanceof Environment) return env.clone() 253 254 Object.defineProperty(this, '_storage', { 255 value: Object.assign({}, env) 256 }) 257} 258Environment.prototype = {} 259 260Environment.prototype.delete = function (key) { 261 var args = Array.isArray(key) ? key : arguments 262 var ii 263 for (ii = 0; ii < args.length; ++ii) { 264 delete this._storage[args[ii]] 265 } 266 return this 267} 268 269Environment.prototype.clone = function () { 270 return new Environment(this._storage) 271} 272 273Environment.prototype.extend = function (env) { 274 var self = this.clone() 275 var args = Array.isArray(env) ? env : arguments 276 var ii 277 for (ii = 0; ii < args.length; ++ii) { 278 var arg = args[ii] 279 if (!arg) continue 280 Object.keys(arg).forEach(function (name) { 281 self._storage[name] = arg[name] 282 }) 283 } 284 return self 285} 286 287var reEscape = /[\\[\](){}*?+.^$|]/g 288exports.escapeForRe = function (string) { 289 return string.replace(reEscape, '\\$&') 290} 291