Skip to content

Commit 96d727b

Browse files
wraithgarruyadorno
authored andcommitted
fix(libnpmexec): don't detach output from npm
The npm output function refers to this.log. libnpmexec has that passed in, which decoupled the function from the npm object. This fixes it, and sets the tests up in a way where if the output function ever becomes detached from this.npm in the same way, tests will fail. PR-URL: #3329 Credit: @wraithgar Close: #3329 Reviewed-by: @ruyadorno
1 parent a7b0b89 commit 96d727b

File tree

6 files changed

+89
-48
lines changed

6 files changed

+89
-48
lines changed

lib/exec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ class Exec extends BaseCommand {
7676
localBin,
7777
log,
7878
globalBin,
79-
output,
8079
} = this.npm
80+
const output = (...outputArgs) => this.npm.output(...outputArgs)
8181
const scriptShell = this.npm.config.get('script-shell') || undefined
8282
const packages = this.npm.config.get('package')
8383
const yes = this.npm.config.get('yes')

lib/init.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,13 @@ class Init extends BaseCommand {
113113
localBin,
114114
log,
115115
globalBin,
116-
output,
117116
} = this.npm
117+
// this function is definitely called. But because of coverage map stuff
118+
// it ends up both uncovered, and the coverage report doesn't even mention.
119+
// the tests do assert that some output happens, so we know this line is
120+
// being hit.
121+
/* istanbul ignore next */
122+
const output = (...outputArgs) => this.npm.output(...outputArgs)
118123
const locationMsg = await getLocationMsg({ color, path })
119124
const runPath = path
120125
const scriptShell = this.npm.config.get('script-shell') || undefined

tap-snapshots/test/lib/init.js.test.cjs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,28 @@
66
*/
77
'use strict'
88
exports[`test/lib/init.js TAP workspaces no args > should print helper info 1`] = `
9-
9+
Array [
10+
Array [
11+
String(
12+
This utility will walk you through creating a package.json file.
13+
It only covers the most common items, and tries to guess sensible defaults.
14+
15+
See \`npm help init\` for definitive documentation on these fields
16+
and exactly what they do.
17+
18+
Use \`npm install <pkg>\` afterwards to install a package and
19+
save it as a dependency in the package.json file.
20+
21+
Press ^C at any time to quit.
22+
),
23+
],
24+
]
1025
`
1126

1227
exports[`test/lib/init.js TAP workspaces no args, existing folder > should print helper info 1`] = `
13-
28+
Array []
1429
`
1530

1631
exports[`test/lib/init.js TAP workspaces with arg but missing workspace folder > should print helper info 1`] = `
17-
32+
Array []
1833
`

test/fixtures/mock-npm.js

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,54 @@
44

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

7-
const mockLog = {
8-
clearProgress: () => {},
9-
disableProgress: () => {},
10-
enableProgress: () => {},
11-
http: () => {},
12-
info: () => {},
13-
levels: [],
14-
notice: () => {},
15-
pause: () => {},
16-
silly: () => {},
17-
verbose: () => {},
18-
warn: () => {},
19-
}
20-
const mockNpm = (base = {}) => {
21-
const config = base.config || {}
22-
const flatOptions = base.flatOptions || {}
23-
return {
24-
log: mockLog,
25-
...base,
26-
flatOptions,
27-
config: {
7+
class MockNpm {
8+
constructor (base = {}) {
9+
this._mockOutputs = []
10+
this.isMockNpm = true
11+
this.base = base
12+
13+
const config = base.config || {}
14+
15+
for (const attr in base) {
16+
if (attr !== 'config') {
17+
this[attr] = base[attr]
18+
}
19+
}
20+
21+
this.flatOptions = base.flatOptions || {}
22+
this.config = {
2823
// for now just set `find` to what config.find should return
2924
// this works cause `find` is not an existing config entry
3025
find: (k) => ({...realConfig.defaults, ...config})[k],
3126
get: (k) => ({...realConfig.defaults, ...config})[k],
3227
set: (k, v) => config[k] = v,
3328
list: [{ ...realConfig.defaults, ...config}]
34-
},
29+
}
30+
if (!this.log) {
31+
this.log = {
32+
clearProgress: () => {},
33+
disableProgress: () => {},
34+
enableProgress: () => {},
35+
http: () => {},
36+
info: () => {},
37+
levels: [],
38+
notice: () => {},
39+
pause: () => {},
40+
silly: () => {},
41+
verbose: () => {},
42+
warn: () => {},
43+
}
44+
}
45+
}
46+
47+
output(...msg) {
48+
if (this.base.output)
49+
return this.base.output(msg)
50+
this._mockOutputs.push(msg)
3551
}
3652
}
3753

38-
module.exports = mockNpm
54+
// TODO export MockNpm, and change tests to use new MockNpm()
55+
module.exports = (base = {}) => {
56+
return new MockNpm(base)
57+
}

test/lib/exec.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
const t = require('tap')
22
const mockNpm = require('../fixtures/mock-npm')
33
const { resolve, delimiter } = require('path')
4-
const OUTPUT = []
5-
const output = (...msg) => OUTPUT.push(msg)
64

75
const ARB_CTOR = []
86
const ARB_ACTUAL_TREE = {}
@@ -36,6 +34,7 @@ const config = {
3634
package: [],
3735
'script-shell': 'shell-cmd',
3836
}
37+
3938
const npm = mockNpm({
4039
flatOptions,
4140
config,
@@ -53,7 +52,6 @@ const npm = mockNpm({
5352
LOG_WARN.push(args)
5453
},
5554
},
56-
output,
5755
})
5856

5957
const RUN_SCRIPTS = []
@@ -225,7 +223,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
225223
ARB_CTOR.length = 0
226224
MKDIRPS.length = 0
227225
ARB_REIFY.length = 0
228-
OUTPUT.length = 0
226+
npm._mockOutputs.length = 0
229227
exec.exec([], er => {
230228
if (er)
231229
throw er
@@ -256,7 +254,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
256254
process.stdin.isTTY = true
257255
run(t, true, () => {
258256
t.strictSame(LOG_WARN, [])
259-
t.strictSame(OUTPUT, [
257+
t.strictSame(npm._mockOutputs, [
260258
[`\nEntering npm script environment at location:\n${process.cwd()}\nType 'exit' or ^D when finished\n`],
261259
], 'printed message about interactive shell')
262260
t.end()
@@ -270,7 +268,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
270268

271269
run(t, true, () => {
272270
t.strictSame(LOG_WARN, [])
273-
t.strictSame(OUTPUT, [
271+
t.strictSame(npm._mockOutputs, [
274272
[`\u001b[0m\u001b[0m\n\u001b[0mEntering npm script environment\u001b[0m\u001b[0m at location:\u001b[0m\n\u001b[0m\u001b[2m${process.cwd()}\u001b[22m\u001b[0m\u001b[1m\u001b[22m\n\u001b[1mType 'exit' or ^D when finished\u001b[22m\n\u001b[1m\u001b[22m`],
275273
], 'printed message about interactive shell')
276274
t.end()
@@ -282,7 +280,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
282280
process.stdin.isTTY = false
283281
run(t, true, () => {
284282
t.strictSame(LOG_WARN, [])
285-
t.strictSame(OUTPUT, [], 'no message about interactive shell')
283+
t.strictSame(npm._mockOutputs, [], 'no message about interactive shell')
286284
t.end()
287285
})
288286
})
@@ -294,7 +292,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
294292
t.strictSame(LOG_WARN, [
295293
['exec', 'Interactive mode disabled in CI environment'],
296294
])
297-
t.strictSame(OUTPUT, [], 'no message about interactive shell')
295+
t.strictSame(npm._mockOutputs, [], 'no message about interactive shell')
298296
t.end()
299297
})
300298
})
@@ -316,7 +314,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
316314
ARB_CTOR.length = 0
317315
MKDIRPS.length = 0
318316
ARB_REIFY.length = 0
319-
OUTPUT.length = 0
317+
npm._mockOutputs.length = 0
320318
RUN_SCRIPTS.length = 0
321319
t.end()
322320
})
@@ -1195,22 +1193,22 @@ t.test('workspaces', t => {
11951193
return rej(er)
11961194

11971195
t.strictSame(LOG_WARN, [])
1198-
t.strictSame(OUTPUT, [
1196+
t.strictSame(npm._mockOutputs, [
11991197
[`\nEntering npm script environment in workspace [email protected] at location:\n${resolve(npm.localPrefix, 'packages/a')}\nType 'exit' or ^D when finished\n`],
12001198
], 'printed message about interactive shell')
12011199
res()
12021200
})
12031201
})
12041202

12051203
config.color = true
1206-
OUTPUT.length = 0
1204+
npm._mockOutputs.length = 0
12071205
await new Promise((res, rej) => {
12081206
exec.execWorkspaces([], ['a'], er => {
12091207
if (er)
12101208
return rej(er)
12111209

12121210
t.strictSame(LOG_WARN, [])
1213-
t.strictSame(OUTPUT, [
1211+
t.strictSame(npm._mockOutputs, [
12141212
[`\u001b[0m\u001b[0m\n\u001b[0mEntering npm script environment\u001b[0m\u001b[0m in workspace \u001b[[email protected]\u001b[39m at location:\u001b[0m\n\u001b[0m\u001b[2m${resolve(npm.localPrefix, 'packages/a')}\u001b[22m\u001b[0m\u001b[1m\u001b[22m\n\u001b[1mType 'exit' or ^D when finished\u001b[22m\n\u001b[1m\u001b[22m`],
12151213
], 'printed message about interactive shell')
12161214
res()

test/lib/init.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const { resolve } = require('path')
33
const t = require('tap')
44
const mockNpm = require('../fixtures/mock-npm')
55

6-
let result = ''
76
const npmLog = {
87
disableProgress: () => null,
98
enableProgress: () => null,
@@ -19,9 +18,6 @@ const config = {
1918
const npm = mockNpm({
2019
config,
2120
log: npmLog,
22-
output: (...msg) => {
23-
result += msg.join('\n')
24-
},
2521
})
2622
const mocks = {
2723
'../../lib/utils/usage.js': () => 'usage instructions',
@@ -33,7 +29,6 @@ const _consolelog = console.log
3329
const noop = () => {}
3430

3531
t.afterEach(() => {
36-
result = ''
3732
config.yes = true
3833
config.package = undefined
3934
npm.log = npmLog
@@ -322,6 +317,9 @@ t.test('npm init error', t => {
322317

323318
t.test('workspaces', t => {
324319
t.test('no args', t => {
320+
t.teardown(() => {
321+
npm._mockOutputs.length = 0
322+
})
325323
npm.localPrefix = t.testdir({
326324
'package.json': JSON.stringify({
327325
name: 'top-level',
@@ -340,12 +338,15 @@ t.test('workspaces', t => {
340338
if (err)
341339
throw err
342340

343-
t.matchSnapshot(result, 'should print helper info')
341+
t.matchSnapshot(npm._mockOutputs, 'should print helper info')
344342
t.end()
345343
})
346344
})
347345

348346
t.test('no args, existing folder', t => {
347+
t.teardown(() => {
348+
npm._mockOutputs.length = 0
349+
})
349350
// init-package-json prints directly to console.log
350351
// this avoids poluting test output with those logs
351352
console.log = noop
@@ -369,12 +370,15 @@ t.test('workspaces', t => {
369370
if (err)
370371
throw err
371372

372-
t.matchSnapshot(result, 'should print helper info')
373+
t.matchSnapshot(npm._mockOutputs, 'should print helper info')
373374
t.end()
374375
})
375376
})
376377

377378
t.test('with arg but missing workspace folder', t => {
379+
t.teardown(() => {
380+
npm._mockOutputs.length = 0
381+
})
378382
// init-package-json prints directly to console.log
379383
// this avoids poluting test output with those logs
380384
console.log = noop
@@ -401,7 +405,7 @@ t.test('workspaces', t => {
401405
if (err)
402406
throw err
403407

404-
t.matchSnapshot(result, 'should print helper info')
408+
t.matchSnapshot(npm._mockOutputs, 'should print helper info')
405409
t.end()
406410
})
407411
})

0 commit comments

Comments
 (0)