Skip to content

Commit 6a8086e

Browse files
committed
fix(tests): move more tests to use real npm
This moves a handful of the smaller tests to using the new npm mock that uses the real actual npm object. It also extends the testing surface area of a few tests back down into the actual `process.spawn` that results, instead of anything internal to the code. Some dead code in `lib/test.js` was found during this, as well as an instance of a module throwing a string instead of an error object. PR-URL: #3463 Credit: @wraithgar Close: #3463 Reviewed-by: @nlf
1 parent e7e1181 commit 6a8086e

18 files changed

+272
-204
lines changed

lib/set.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Set extends BaseCommand {
2222

2323
exec (args, cb) {
2424
if (!args.length)
25-
return cb(this.usage)
25+
return cb(this.usageError())
2626
this.npm.commands.config(['set'].concat(args), cb)
2727
}
2828
}

lib/test.js

-10
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,5 @@ class Test extends LifecycleCmd {
1919
'script-shell',
2020
]
2121
}
22-
23-
exec (args, cb) {
24-
super.exec(args, er => {
25-
if (er && er.code === 'ELIFECYCLE') {
26-
/* eslint-disable standard/no-callback-literal */
27-
cb('Test failed. See above for more details.')
28-
} else
29-
cb(er)
30-
})
31-
}
3222
}
3323
module.exports = Test

node_modules/.gitignore

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@
199199
"eslint-plugin-promise": "^5.1.0",
200200
"eslint-plugin-standard": "^5.0.0",
201201
"licensee": "^8.2.0",
202+
"spawk": "^1.7.1",
202203
"tap": "^15.0.9"
203204
},
204205
"scripts": {

test/fixtures/mock-npm.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ for (const level in npmlog.levels)
99
const { title, execPath } = process
1010

1111
const RealMockNpm = (t, otherMocks = {}) => {
12+
t.afterEach(() => {
13+
outputs.length = 0
14+
logs.length = 0
15+
})
1216
t.teardown(() => {
1317
npm.perfStop()
1418
npmlog.record.length = 0
@@ -22,6 +26,9 @@ const RealMockNpm = (t, otherMocks = {}) => {
2226
})
2327
const logs = []
2428
const outputs = []
29+
const joinedOutput = () => {
30+
return outputs.map(o => o.join(' ')).join('\n')
31+
}
2532
const npm = t.mock('../../lib/npm.js', otherMocks)
2633
const command = async (command, args = []) => {
2734
return new Promise((resolve, reject) => {
@@ -43,7 +50,7 @@ const RealMockNpm = (t, otherMocks = {}) => {
4350
}
4451
}
4552
npm.output = (...msg) => outputs.push(msg)
46-
return { npm, logs, outputs, command }
53+
return { npm, logs, outputs, command, joinedOutput }
4754
}
4855

4956
const realConfig = require('../../lib/utils/config')

test/lib/find-dupes.js

+19-17
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
const t = require('tap')
22

3-
const FindDupes = require('../../lib/find-dupes.js')
3+
const { real: mockNpm } = require('../fixtures/mock-npm')
44

5-
t.test('should run dedupe in dryRun mode', (t) => {
6-
t.plan(3)
7-
const findDupesTest = new FindDupes({
8-
config: {
9-
set: (k, v) => {
10-
t.match(k, 'dry-run')
11-
t.match(v, true)
12-
},
5+
t.test('should run dedupe in dryRun mode', async (t) => {
6+
t.plan(5)
7+
const { npm, command } = mockNpm(t, {
8+
'@npmcli/arborist': function (args) {
9+
t.ok(args, 'gets options object')
10+
t.ok(args.path, 'gets path option')
11+
t.ok(args.dryRun, 'is called in dryRun mode')
12+
this.dedupe = () => {
13+
t.ok(true, 'dedupe is called')
14+
}
1315
},
14-
commands: {
15-
dedupe: (args, cb) => {
16-
t.match(args, [])
17-
cb()
18-
},
16+
'../../lib/utils/reify-finish.js': (npm, arb) => {
17+
t.ok(arb, 'gets arborist tree')
1918
},
2019
})
21-
findDupesTest.exec({}, () => {
22-
t.end()
23-
})
20+
await npm.load()
21+
// explicitly set to false so we can be 100% sure it's always true when it
22+
// hits arborist
23+
npm.config.set('dry-run', false)
24+
npm.config.set('prefix', 'foo')
25+
await command('find-dupes')
2426
})

test/lib/get.js

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
const t = require('tap')
2+
const { real: mockNpm } = require('../fixtures/mock-npm')
23

3-
t.test('should retrieve values from npm.commands.config', (t) => {
4-
const Get = t.mock('../../lib/get.js')
5-
const get = new Get({
6-
commands: {
7-
config: ([action, arg]) => {
8-
t.equal(action, 'get', 'should use config get action')
9-
t.equal(arg, 'foo', 'should use expected key')
10-
t.end()
11-
},
12-
},
13-
})
14-
15-
get.exec(['foo'])
4+
t.test('should retrieve values from config', async t => {
5+
const { joinedOutput, command, npm } = mockNpm(t)
6+
const name = 'editor'
7+
const value = 'vigor'
8+
await npm.load()
9+
npm.config.set(name, value)
10+
await command('get', [name])
11+
t.equal(
12+
joinedOutput(),
13+
value,
14+
'outputs config item'
15+
)
1616
})

test/lib/load-all.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
const t = require('tap')
22
const glob = require('glob')
33
const { resolve } = require('path')
4+
const { real: mockNpm } = require('../fixtures/mock-npm')
45

56
const full = process.env.npm_lifecycle_event === 'check-coverage'
67

78
if (!full)
89
t.pass('nothing to do here, not checking for full coverage')
910
else {
10-
// some files do config.get() on load, so have to load npm first
11-
const npm = require('../../lib/npm.js')
12-
t.test('load npm first', t => npm.load(t.end))
11+
const { npm } = mockNpm(t)
12+
13+
t.teardown(() => {
14+
const exitHandler = require('../../lib/utils/exit-handler.js')
15+
exitHandler.setNpm(npm)
16+
exitHandler()
17+
})
18+
19+
t.test('load npm first', async t => {
20+
await npm.load()
21+
})
1322

1423
t.test('load all the files', t => {
1524
// just load all the files so we measure coverage for the missing tests
@@ -21,11 +30,4 @@ else {
2130
t.pass('loaded all files')
2231
t.end()
2332
})
24-
25-
t.test('call the exit handler so we dont freak out', t => {
26-
const exitHandler = require('../../lib/utils/exit-handler.js')
27-
exitHandler.setNpm(npm)
28-
exitHandler()
29-
t.end()
30-
})
3133
}

test/lib/prefix.js

+10-16
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
const t = require('tap')
2+
const { real: mockNpm } = require('../fixtures/mock-npm')
23

3-
t.test('prefix', (t) => {
4-
t.plan(3)
5-
const dir = '/prefix/dir'
6-
7-
const Prefix = require('../../lib/prefix.js')
8-
const prefix = new Prefix({
9-
prefix: dir,
10-
output: (output) => {
11-
t.equal(output, dir, 'prints the correct directory')
12-
},
13-
})
14-
15-
prefix.exec([], (err) => {
16-
t.error(err, 'npm prefix')
17-
t.ok('should have printed directory')
18-
})
4+
t.test('prefix', async (t) => {
5+
const { joinedOutput, command, npm } = mockNpm(t)
6+
await npm.load()
7+
await command('prefix')
8+
t.equal(
9+
joinedOutput(),
10+
npm.prefix,
11+
'outputs npm.prefix'
12+
)
1913
})

test/lib/prune.js

+6-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
const t = require('tap')
2+
const { real: mockNpm } = require('../fixtures/mock-npm')
23

3-
t.test('should prune using Arborist', (t) => {
4-
const Prune = t.mock('../../lib/prune.js', {
4+
t.test('should prune using Arborist', async (t) => {
5+
t.plan(4)
6+
const { command, npm } = mockNpm(t, {
57
'@npmcli/arborist': function (args) {
68
t.ok(args, 'gets options object')
79
t.ok(args.path, 'gets path option')
@@ -13,16 +15,6 @@ t.test('should prune using Arborist', (t) => {
1315
t.ok(arb, 'gets arborist tree')
1416
},
1517
})
16-
const prune = new Prune({
17-
prefix: 'foo',
18-
flatOptions: {
19-
foo: 'bar',
20-
},
21-
})
22-
prune.exec(null, er => {
23-
if (er)
24-
throw er
25-
t.ok(true, 'callback is called')
26-
t.end()
27-
})
18+
await npm.load()
19+
await command('prune')
2820
})

test/lib/restart.js

+34-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,36 @@
11
const t = require('tap')
2-
let runArgs
3-
const npm = {
4-
commands: {
5-
'run-script': (args, cb) => {
6-
runArgs = args
7-
cb()
8-
},
9-
},
10-
}
11-
const Restart = require('../../lib/restart.js')
12-
const restart = new Restart(npm)
13-
restart.exec(['foo'], () => {
14-
t.match(runArgs, ['restart', 'foo'])
15-
t.end()
2+
const spawk = require('spawk')
3+
const { real: mockNpm } = require('../fixtures/mock-npm')
4+
5+
spawk.preventUnmatched()
6+
t.teardown(() => {
7+
spawk.unload()
8+
})
9+
10+
// TODO this ... smells. npm "script-shell" config mentions defaults but those
11+
// are handled by run-script, not npm. So for now we have to tie tests to some
12+
// pretty specific internals of runScript
13+
const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js')
14+
15+
t.test('should run stop script from package.json', async t => {
16+
const prefix = t.testdir({
17+
'package.json': JSON.stringify({
18+
name: 'x',
19+
version: '1.2.3',
20+
scripts: {
21+
restart: 'node ./test-restart.js',
22+
},
23+
}),
24+
})
25+
const { command, npm } = mockNpm(t)
26+
await npm.load()
27+
npm.log.level = 'silent'
28+
npm.localPrefix = prefix
29+
const [scriptShell] = makeSpawnArgs({ path: prefix })
30+
const script = spawk.spawn(scriptShell, (args) => {
31+
t.ok(args.includes('node ./test-restart.js "foo"'), 'ran stop script with extra args')
32+
return true
33+
})
34+
await command('restart', ['foo'])
35+
t.ok(script.called, 'script ran')
1636
})

test/lib/root.js

+10-16
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
const t = require('tap')
2+
const { real: mockNpm } = require('../fixtures/mock-npm')
23

3-
t.test('root', (t) => {
4-
t.plan(3)
5-
const dir = '/root/dir'
6-
7-
const Root = require('../../lib/root.js')
8-
const root = new Root({
9-
dir,
10-
output: (output) => {
11-
t.equal(output, dir, 'prints the correct directory')
12-
},
13-
})
14-
15-
root.exec([], (err) => {
16-
t.error(err, 'npm root')
17-
t.ok('should have printed directory')
18-
})
4+
t.test('prefix', async (t) => {
5+
const { joinedOutput, command, npm } = mockNpm(t)
6+
await npm.load()
7+
await command('root')
8+
t.equal(
9+
joinedOutput(),
10+
npm.dir,
11+
'outputs npm.dir'
12+
)
1913
})

test/lib/set.js

+27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
const t = require('tap')
22

3+
// can't run this until npm set can save to npm.localPrefix
4+
t.skip('npm set', async t => {
5+
const { real: mockNpm } = require('../fixtures/mock-npm')
6+
const { joinedOutput, command, npm } = mockNpm(t)
7+
await npm.load()
8+
9+
t.test('no args', async t => {
10+
t.rejects(
11+
command('set'),
12+
/Usage:/,
13+
'prints usage'
14+
)
15+
})
16+
17+
t.test('test-config-item', async t => {
18+
npm.localPrefix = t.testdir({})
19+
t.not(npm.config.get('test-config-item', 'project'), 'test config value', 'config is not already new value')
20+
// This will write to ~/.npmrc!
21+
// Don't unskip until we can write to project level
22+
await command('set', ['test-config-item=test config value'])
23+
t.equal(joinedOutput(), '', 'outputs nothing')
24+
t.equal(npm.config.get('test-config-item', 'project'), 'test config value', 'config is set to new value')
25+
})
26+
})
27+
28+
// Everything after this can go away once the above test is unskipped
29+
330
let configArgs = null
431
const npm = {
532
commands: {

0 commit comments

Comments
 (0)