1var fs = require('fs') 2var path = require('path') 3 4var mkdirp = require('mkdirp') 5var rimraf = require('rimraf') 6var which = require('which') 7var test = require('tap').test 8 9var common = require('../common-tap.js') 10var isWindows = require('../../lib/utils/is-windows.js') 11 12var pkg = common.pkg 13 14var PATH 15if (isWindows) { 16 // On Windows the 'comspec' environment variable is used, 17 // so cmd.exe does not need to be on the path. 18 PATH = path.dirname(process.env.ComSpec) 19} else { 20 // On non-Windows, without the path to the shell, nothing usually works. 21 PATH = '/bin:/usr/bin' 22} 23 24var systemNode = which.sync('node', { nothrow: true, path: PATH }) 25// the path to the system wide node (null if none) 26 27test('setup', function (t) { 28 fs.writeFileSync( 29 path.join(pkg, 'package.json'), 30 JSON.stringify({}, null, 2) 31 ) 32 t.end() 33}) 34 35test('make sure the path is correct, without directory of current node', function (t) { 36 checkPath({ 37 withDirOfCurrentNode: false, 38 prependNodePathSetting: false 39 }, t) 40}) 41 42test('make sure the path is correct, with directory of current node', function (t) { 43 checkPath({ 44 withDirOfCurrentNode: true, 45 prependNodePathSetting: false 46 }, t) 47}) 48 49test('make sure the path is correct, with directory of current node but ignored node path', function (t) { 50 checkPath({ 51 withDirOfCurrentNode: true, 52 prependNodePathSetting: true 53 }, t) 54}) 55 56test('make sure the path is correct, without directory of current node and automatic detection', function (t) { 57 checkPath({ 58 withDirOfCurrentNode: false, 59 prependNodePathSetting: 'auto' 60 }, t) 61}) 62 63test('make sure the path is correct, with directory of current node and automatic detection', function (t) { 64 checkPath({ 65 withDirOfCurrentNode: true, 66 prependNodePathSetting: 'auto' 67 }, t) 68}) 69 70test('make sure the path is correct, without directory of current node and warn-only detection', function (t) { 71 checkPath({ 72 withDirOfCurrentNode: false, 73 prependNodePathSetting: 'warn-only' 74 }, t) 75}) 76 77test('make sure the path is correct, with directory of current node and warn-only detection', function (t) { 78 checkPath({ 79 withDirOfCurrentNode: true, 80 prependNodePathSetting: 'warn-only' 81 }, t) 82}) 83 84test('make sure there is no warning with a symlinked node and warn-only detection', { 85 skip: isWindows && 'symlinks are weird on windows' 86}, function (t) { 87 checkPath({ 88 withDirOfCurrentNode: false, 89 extraNode: true, 90 prependNodePathSetting: 'warn-only', 91 symlinkNodeInsteadOfCopying: true 92 }, t) 93}) 94 95test('make sure the path is correct, with directory of current node and warn-only detection and an extra node in path', function (t) { 96 checkPath({ 97 withDirOfCurrentNode: false, 98 extraNode: true, 99 prependNodePathSetting: 'warn-only' 100 }, t) 101}) 102 103function checkPath (testconfig, t) { 104 var withDirOfCurrentNode = testconfig.withDirOfCurrentNode 105 var prependNodePathSetting = testconfig.prependNodePathSetting 106 var symlinkedNode = testconfig.symlinkNodeInsteadOfCopying 107 var extraNode = testconfig.extraNode 108 109 var newPATH = PATH 110 var currentNodeExecPath = process.execPath 111 if (withDirOfCurrentNode) { 112 var newNodeExeDir = path.join(pkg, 'node-bin', 'my_bundled_node') 113 mkdirp.sync(newNodeExeDir) 114 currentNodeExecPath = path.join(newNodeExeDir, path.basename(process.execPath)) 115 rimraf.sync(currentNodeExecPath) 116 117 if (!symlinkedNode) { 118 fs.writeFileSync(currentNodeExecPath, fs.readFileSync(process.execPath)) 119 fs.chmodSync(currentNodeExecPath, '755') 120 } else { 121 fs.symlinkSync(process.execPath, currentNodeExecPath) 122 } 123 } 124 125 if (!withDirOfCurrentNode) { 126 // Ensure that current node interpreter will be found in the PATH, 127 // so the PATH won't be prepended with its parent directory 128 newPATH = [path.dirname(process.execPath), PATH].join(process.platform === 'win32' ? ';' : ':') 129 } 130 131 common.npm(['run-script', 'env'], { 132 cwd: pkg, 133 nodeExecPath: currentNodeExecPath, 134 env: { 135 PATH: newPATH, 136 npm_config_scripts_prepend_node_path: prependNodePathSetting 137 }, 138 stdio: [ 0, 'pipe', 'pipe' ] 139 }, function (er, code, stdout, stderr) { 140 if (er) throw er 141 if (!stderr.match(/^(npm WARN.*)?\n*$/)) console.error(stderr) 142 t.equal(code, 0, 'exit code') 143 var lineMatch = function (line) { 144 return /^PATH=/i.test(line) 145 } 146 // extract just the path value 147 stdout = stdout.split(/\r?\n/) 148 var observedPath = stdout.filter(lineMatch).pop().replace(/^PATH=/, '') 149 var pathSplit = process.platform === 'win32' ? ';' : ':' 150 var root = path.resolve(__dirname, '../..') 151 var actual = observedPath.split(pathSplit).map(function (p) { 152 if (p.indexOf(pkg) === 0) { 153 p = '{{PKG}}' + p.substr(pkg.length) 154 } 155 if (p.indexOf(root) === 0) { 156 p = '{{ROOT}}' + p.substr(root.length) 157 } 158 return p.replace(/\\/g, '/') 159 }) 160 // spawn-wrap adds itself to the path when coverage is enabled 161 actual = actual.filter(function (p) { 162 return !p.match(/\.node-spawn-wrap/) 163 }) 164 165 // get the ones we tacked on, then the system-specific requirements 166 var expectedPaths = ['{{ROOT}}/node_modules/npm-lifecycle/node-gyp-bin', 167 '{{PKG}}/node_modules/.bin'] 168 169 // Check that the behaviour matches the configuration that was actually 170 // used by the child process, as the coverage tooling may set the 171 // --scripts-prepend-node-path option on its own. 172 var realPrependNodePathSetting = stdout.filter(function (line) { 173 return line.match(/npm_config_scripts_prepend_node_path=(true|auto)/) 174 }).length > 0 175 176 if (prependNodePathSetting === 'warn-only') { 177 if (symlinkedNode) { 178 t.equal(stderr, '', 'does not spit out a warning') 179 } else if (withDirOfCurrentNode) { 180 t.match(stderr, /npm WARN lifecycle/, 'spit out a warning') 181 t.match(stderr, /npm is using .*node-bin.my_bundled_node(.exe)?/, 'mention the path of the binary npm itself is using.') 182 if (extraNode) { 183 var regex = new RegExp( 184 'The node binary used for scripts is.*' + 185 process.execPath.replace(/[/\\]/g, '.')) 186 t.match(stderr, regex, 'reports the current binary vs conflicting') 187 } else if (systemNode !== null) { 188 var regexSystemNode = new RegExp( 189 'The node binary used for scripts is.*' + 190 systemNode.replace(/[/\\]/g, '.') 191 ) 192 t.match(stderr, regexSystemNode, 'reports the system binary vs conflicting') 193 } else { 194 t.match(stderr, /there is no node binary in the current PATH/, 'informs user that there is no node binary in PATH') 195 } 196 } else { 197 t.same(stderr, '') 198 } 199 } 200 201 if (withDirOfCurrentNode && realPrependNodePathSetting) { 202 expectedPaths.push('{{PKG}}/node-bin/my_bundled_node') 203 } 204 var expect = expectedPaths.concat(newPATH.split(pathSplit)).map(function (p) { 205 return p.replace(/\\/g, '/') 206 }) 207 t.same(actual, expect) 208 t.end() 209 }) 210} 211