• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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