• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* eslint-disable camelcase */
2var execFile = require('child_process').execFile
3var path = require('path')
4var zlib = require('zlib')
5
6var asyncMap = require('slide').asyncMap
7var deepEqual = require('deep-equal')
8var fs = require('graceful-fs')
9var mkdirp = require('mkdirp')
10var once = require('once')
11var requireInject = require('require-inject')
12var rimraf = require('rimraf')
13var tar = require('tar')
14var test = require('tap').test
15var tmpdir = require('osenv').tmpdir
16var which = require('which')
17
18var wd = path.resolve(tmpdir(), 'git-races')
19var fixtures = path.resolve(__dirname, '../fixtures')
20var testcase = 'github-com-BryanDonovan-npm-git-test'
21var testcase_git = path.resolve(wd, testcase + '.git')
22var testcase_path = path.resolve(wd, testcase)
23var testcase_tgz = path.resolve(fixtures, testcase + '.git.tar.gz')
24
25var testtarballs = []
26var testrepos = {}
27var testurls = {}
28
29/*
30This test is specifically for #7202, where the bug was if you tried installing multiple git urls that
31pointed at the same repo but had different comittishes, you'd sometimes get the wrong version.
32The test cases, provided by @BryanDonovan, have a dependency tree like this:
33
34  top
35    bar#4.0.0
36      buzz#3.0.0
37      foo#3.0.0
38    buzz#3.0.0
39    foo#4.0.0
40      buzz#2.0.0
41
42But what would happen is that buzz#2.0.0 would end up installed under bar#4.0.0.
43
44bar#4.0.0 shouldn't have gotten its own copy if buzz, and if it did, it shouldn've been buzz#3.0.0
45*/
46
47;['bar', 'foo', 'buzz'].forEach(function (name) {
48  var mockurl = 'ssh://git@github.com/BryanDonovan/dummy-npm-' + name + '.git'
49  var realrepo = path.resolve(wd, 'github-com-BryanDonovan-dummy-npm-' + name + '.git')
50  var tgz = path.resolve(fixtures, 'github-com-BryanDonovan-dummy-npm-' + name + '.git.tar.gz')
51
52  testrepos[mockurl] = realrepo
53  testtarballs.push(tgz)
54})
55
56function cleanup () {
57  process.chdir(tmpdir())
58  rimraf.sync(wd)
59}
60
61var npm = requireInject.installGlobally('../../lib/npm.js', {
62  'child_process': {
63    'execFile': function (cmd, args, options, cb) {
64      // on win 32, the following prefix is added in utils/git.js
65      // $ git -c core.longpaths=true clone
66      var i = process.platform === 'win32' ? 2 : 0
67      // If it's a clone we swap any requests for any of the urls we're mocking
68      // with the path to the bare repo
69      if (args[i] === 'clone') {
70        var m2 = args.length - 2
71        var m1 = args.length - 1
72        if (testrepos[args[m2]]) {
73          testurls[args[m1]] = args[m2]
74          args[m2] = testrepos[args[m2]]
75        }
76        execFile(cmd, args, options, cb)
77      // here, we intercept npm validating the remote origin url on one of the
78      // clones we've done previously and return the original url that was requested
79      } else if (args[i] === 'config' && args[i + 1] === '--get' && args[i + 2] === 'remote.origin.url') {
80        process.nextTick(function () {
81          cb(null, testurls[options.cwd], '')
82        })
83      } else {
84        execFile(cmd, args, options, cb)
85      }
86    }
87  }
88})
89
90function extract (tarball, target, cb) {
91  cb = once(cb)
92  fs.createReadStream(tarball).on('error', function (er) { cb(er) })
93    .pipe(zlib.createGunzip()).on('error', function (er) { cb(er) })
94    .pipe(tar.Extract({path: target})).on('error', function (er) { cb(er) })
95    .on('end', function () {
96      cb()
97    })
98}
99
100// Copied from lib/utils/git, because we need to use
101// it before calling npm.load and lib/utils/git uses npm.js
102// which doesn't allow that. =( =(
103
104function prefixGitArgs () {
105  return process.platform === 'win32' ? ['-c', 'core.longpaths=true'] : []
106}
107
108var gitcmd
109
110function execGit (args, options, cb) {
111  var fullArgs = prefixGitArgs().concat(args || [])
112  return execFile(gitcmd, fullArgs, options, cb)
113}
114
115function gitWhichAndExec (args, options, cb) {
116  if (gitcmd) return execGit(args, options, cb)
117
118  which('git', function (err, pathtogit) {
119    if (err) {
120      err.code = 'ENOGIT'
121      return cb(err)
122    }
123    gitcmd = pathtogit
124
125    execGit(args, options, cb)
126  })
127}
128
129function andClone (gitdir, repodir, cb) {
130  return function (er) {
131    if (er) return cb(er)
132    gitWhichAndExec(['clone', gitdir, repodir], {}, cb)
133  }
134}
135
136function setup (cb) {
137  cleanup()
138  mkdirp.sync(wd)
139
140  extract(testcase_tgz, wd, andClone(testcase_git, testcase_path, andExtractPackages))
141
142  function andExtractPackages (er) {
143    if (er) return cb(er)
144    asyncMap(testtarballs, function (tgz, done) {
145      extract(tgz, wd, done)
146    }, andChdir)
147  }
148  function andChdir (er) {
149    if (er) return cb(er)
150    process.chdir(testcase_path)
151    andLoadNpm()
152  }
153  function andLoadNpm () {
154    var opts = {
155      cache: path.resolve(wd, 'cache')
156    }
157    npm.load(opts, cb)
158  }
159}
160
161// there are two (sic) valid trees that can result we don't care which one we
162// get in npm@2
163var oneTree = [
164  'npm-git-test@1.0.0', [
165    ['dummy-npm-bar@4.0.0', [
166      ['dummy-npm-foo@3.0.0', []],
167      ['dummy-npm-buzz@3.0.0', []]
168    ]],
169    ['dummy-npm-buzz@3.0.0', []],
170    ['dummy-npm-foo@4.0.0', [
171      ['dummy-npm-buzz@2.0.0', []]
172    ]]
173  ]
174]
175var otherTree = [
176  'npm-git-test@1.0.0', [
177    ['dummy-npm-bar@4.0.0', [
178      ['dummy-npm-buzz@3.0.0', []],
179      ['dummy-npm-foo@3.0.0', []]
180    ]],
181    ['dummy-npm-buzz@3.0.0', []],
182    ['dummy-npm-foo@4.0.0', [
183      ['dummy-npm-buzz@2.0.0', []]
184    ]]
185  ]
186]
187
188function toSimple (tree) {
189  var deps = []
190  Object.keys(tree.dependencies || {}).forEach(function (dep) {
191    deps.push(toSimple(tree.dependencies[dep]))
192  })
193  return [ tree['name'] + '@' + tree['version'], deps ]
194}
195
196test('setup', function (t) {
197  setup(function (er) {
198    t.ifError(er, 'setup ran OK')
199    t.end()
200  })
201})
202
203test('correct versions are installed for git dependency', function (t) {
204  t.comment('test for https://github.com/npm/npm/issues/7202')
205  npm.commands.install([], function (er) {
206    t.ifError(er, 'installed OK')
207    npm.commands.ls([], true, function (er, result) {
208      t.ifError(er, 'ls OK')
209      var simplified = toSimple(result)
210      if (deepEqual(simplified, oneTree) || deepEqual(simplified, otherTree)) {
211        t.pass('install tree is correct')
212      } else {
213        t.isDeeply(simplified, oneTree, 'oneTree matches')
214        t.isDeeply(simplified, otherTree, 'otherTree matches')
215      }
216      t.done()
217    })
218  })
219})
220