1 var execFile = require('child_process').execFile
2 var path = require('path')
3 var zlib = require('zlib')
5 var asyncMap = require('slide').asyncMap
6 var deepEqual = require('deep-equal')
7 var fs = require('graceful-fs')
8 var mkdirp = require('mkdirp')
9 var once = require('once')
10 var requireInject = require('require-inject')
11 var rimraf = require('rimraf')
12 var tar = require('tar')
13 var test = require('tap').test
14 var tmpdir = require('osenv').tmpdir
15 var which = require('which')
17 var wd = path.resolve(tmpdir(), 'git-races')
18 var fixtures = path.resolve(__dirname, '../fixtures')
19 var testcase = 'github-com-BryanDonovan-npm-git-test'
20 var testcase_git = path.resolve(wd, testcase + '.git')
21 var testcase_path = path.resolve(wd, testcase)
22 var testcase_tgz = path.resolve(fixtures, testcase + '.git.tar.gz')
29 This test is specifically for #7202, where the bug was if you tried installing multiple git urls that
30 pointed at the same repo but had different comittishes, you'd sometimes get the wrong version.
31 The test cases, provided by @BryanDonovan, have a dependency tree like this:
41 But what would happen is that buzz#2.0.0 would end up installed under bar#4.0.0.
43 bar#4.0.0 shouldn't have gotten its own copy if buzz, and if it did, it shouldn've been buzz#3.0.0
46 ;['bar', 'foo', 'buzz'].forEach(function (name) {
47 var mockurl = 'ssh://git@github.com/BryanDonovan/dummy-npm-' + name + '.git'
48 var realrepo = path.resolve(wd, 'github-com-BryanDonovan-dummy-npm-' + name + '.git')
49 var tgz = path.resolve(fixtures, 'github-com-BryanDonovan-dummy-npm-' + name + '.git.tar.gz')
51 testrepos[mockurl] = realrepo
52 testtarballs.push(tgz)
56 process.chdir(tmpdir())
60 var npm = requireInject.installGlobally('../../lib/npm.js', {
62 'execFile': function (cmd, args, options, cb) {
63 // If it's a clone we swap any requests for any of the urls we're mocking
64 // with the path to the bare repo
65 if (args[0] === 'clone') {
66 var m2 = args.length - 2
67 var m1 = args.length - 1
68 if (testrepos[args[m2]]) {
69 testurls[args[m1]] = args[m2]
70 args[m2] = testrepos[args[m2]]
72 execFile(cmd, args, options, cb)
73 // here, we intercept npm validating the remote origin url on one of the
74 // clones we've done previously and return the original url that was requested
75 } else if (args[0] === 'config' && args[1] === '--get' && args[2] === 'remote.origin.url') {
76 process.nextTick(function () {
77 cb(null, testurls[options.cwd], '')
80 execFile(cmd, args, options, cb)
86 function extract (tarball, target, cb) {
88 fs.createReadStream(tarball).on('error', function (er) { cb(er) })
89 .pipe(zlib.createGunzip()).on('error', function (er) { cb(er) })
90 .pipe(tar.Extract({path: target})).on('error', function (er) { cb(er) })
91 .on('end', function () {
96 // Copied from lib/utils/git, because we need to use
97 // it before calling npm.load and lib/utils/git uses npm.js
98 // which doesn't allow that. =( =(
100 function prefixGitArgs () {
101 return process.platform === 'win32' ? ['-c', 'core.longpaths=true'] : []
106 function execGit (args, options, cb) {
107 var fullArgs = prefixGitArgs().concat(args || [])
108 return execFile(gitcmd, fullArgs, options, cb)
111 function gitWhichAndExec (args, options, cb) {
112 if (gitcmd) return execGit(args, options, cb)
114 which('git', function (err, pathtogit) {
121 execGit(args, options, cb)
125 function andClone (gitdir, repodir, cb) {
126 return function (er) {
127 if (er) return cb(er)
128 gitWhichAndExec(['clone', gitdir, repodir], {}, cb)
132 function setup (cb) {
136 extract(testcase_tgz, wd, andClone(testcase_git, testcase_path, andExtractPackages))
138 function andExtractPackages (er) {
139 if (er) return cb(er)
140 asyncMap(testtarballs, function (tgz, done) {
141 extract(tgz, wd, done)
144 function andChdir (er) {
145 if (er) return cb(er)
146 process.chdir(testcase_path)
149 function andLoadNpm () {
151 cache: path.resolve(wd, 'cache')
157 // there are two (sic) valid trees that can result we don't care which one we
160 'npm-git-test@1.0.0', [
161 ['dummy-npm-bar@4.0.0', [
162 ['dummy-npm-foo@3.0.0', []]
164 ['dummy-npm-buzz@3.0.0', []],
165 ['dummy-npm-foo@4.0.0', [
166 ['dummy-npm-buzz@2.0.0', []]
171 'npm-git-test@1.0.0', [
172 ['dummy-npm-bar@4.0.0', [
173 ['dummy-npm-buzz@3.0.0', []],
174 ['dummy-npm-foo@3.0.0', []]
176 ['dummy-npm-buzz@3.0.0', []],
177 ['dummy-npm-foo@4.0.0', [
178 ['dummy-npm-buzz@2.0.0', []]
183 function toSimple (tree) {
185 Object.keys(tree.dependencies || {}).forEach(function (dep) {
186 deps.push(toSimple(tree.dependencies[dep]))
188 return [ tree['name'] + '@' + tree['version'], deps ]
191 test('setup', function (t) {
192 setup(function (er) {
193 t.ifError(er, 'setup ran OK')
198 test('correct versions are installed for git dependency', function (t) {
200 t.comment('test for https://github.com/npm/npm/issues/7202')
201 npm.commands.install([], function (er) {
202 t.ifError(er, 'installed OK')
203 npm.commands.ls([], true, function (er, result) {
204 t.ifError(er, 'ls OK')
205 var simplified = toSimple(result)
207 deepEqual(simplified, oneTree) || deepEqual(simplified, otherTree),
208 'install tree is correct'