Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(libnpmexec): don't detach output from npm #3329

Merged
merged 1 commit into from May 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/exec.js
Expand Up @@ -76,8 +76,8 @@ class Exec extends BaseCommand {
localBin,
log,
globalBin,
output,
} = this.npm
const output = (...outputArgs) => this.npm.output(...outputArgs)
const scriptShell = this.npm.config.get('script-shell') || undefined
const packages = this.npm.config.get('package')
const yes = this.npm.config.get('yes')
Expand Down
7 changes: 6 additions & 1 deletion lib/init.js
Expand Up @@ -113,8 +113,13 @@ class Init extends BaseCommand {
localBin,
log,
globalBin,
output,
} = this.npm
// this function is definitely called. But because of coverage map stuff
// it ends up both uncovered, and the coverage report doesn't even mention.
// the tests do assert that some output happens, so we know this line is
// being hit.
/* istanbul ignore next */
const output = (...outputArgs) => this.npm.output(...outputArgs)
const locationMsg = await getLocationMsg({ color, path })
const runPath = path
const scriptShell = this.npm.config.get('script-shell') || undefined
Expand Down
21 changes: 18 additions & 3 deletions tap-snapshots/test/lib/init.js.test.cjs
Expand Up @@ -6,13 +6,28 @@
*/
'use strict'
exports[`test/lib/init.js TAP workspaces no args > should print helper info 1`] = `
Array [
Array [
String(
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See \`npm help init\` for definitive documentation on these fields
and exactly what they do.
Use \`npm install <pkg>\` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
),
],
]
`

exports[`test/lib/init.js TAP workspaces no args, existing folder > should print helper info 1`] = `
Array []
`

exports[`test/lib/init.js TAP workspaces with arg but missing workspace folder > should print helper info 1`] = `
Array []
`
65 changes: 42 additions & 23 deletions test/fixtures/mock-npm.js
Expand Up @@ -4,35 +4,54 @@

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

const mockLog = {
clearProgress: () => {},
disableProgress: () => {},
enableProgress: () => {},
http: () => {},
info: () => {},
levels: [],
notice: () => {},
pause: () => {},
silly: () => {},
verbose: () => {},
warn: () => {},
}
const mockNpm = (base = {}) => {
const config = base.config || {}
const flatOptions = base.flatOptions || {}
return {
log: mockLog,
...base,
flatOptions,
config: {
class MockNpm {
constructor (base = {}) {
this._mockOutputs = []
this.isMockNpm = true
this.base = base

const config = base.config || {}

for (const attr in base) {
if (attr !== 'config') {
this[attr] = base[attr]
}
}

this.flatOptions = base.flatOptions || {}
this.config = {
// for now just set `find` to what config.find should return
// this works cause `find` is not an existing config entry
find: (k) => ({...realConfig.defaults, ...config})[k],
get: (k) => ({...realConfig.defaults, ...config})[k],
set: (k, v) => config[k] = v,
list: [{ ...realConfig.defaults, ...config}]
},
}
if (!this.log) {
this.log = {
clearProgress: () => {},
disableProgress: () => {},
enableProgress: () => {},
http: () => {},
info: () => {},
levels: [],
notice: () => {},
pause: () => {},
silly: () => {},
verbose: () => {},
warn: () => {},
}
}
}

output(...msg) {
if (this.base.output)
return this.base.output(msg)
this._mockOutputs.push(msg)
}
}

module.exports = mockNpm
// TODO export MockNpm, and change tests to use new MockNpm()
module.exports = (base = {}) => {
return new MockNpm(base)
}
22 changes: 10 additions & 12 deletions test/lib/exec.js
@@ -1,8 +1,6 @@
const t = require('tap')
const mockNpm = require('../fixtures/mock-npm')
const { resolve, delimiter } = require('path')
const OUTPUT = []
const output = (...msg) => OUTPUT.push(msg)

const ARB_CTOR = []
const ARB_ACTUAL_TREE = {}
Expand Down Expand Up @@ -36,6 +34,7 @@ const config = {
package: [],
'script-shell': 'shell-cmd',
}

const npm = mockNpm({
flatOptions,
config,
Expand All @@ -53,7 +52,6 @@ const npm = mockNpm({
LOG_WARN.push(args)
},
},
output,
})

const RUN_SCRIPTS = []
Expand Down Expand Up @@ -225,7 +223,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
ARB_CTOR.length = 0
MKDIRPS.length = 0
ARB_REIFY.length = 0
OUTPUT.length = 0
npm._mockOutputs.length = 0
exec.exec([], er => {
if (er)
throw er
Expand Down Expand Up @@ -256,7 +254,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
process.stdin.isTTY = true
run(t, true, () => {
t.strictSame(LOG_WARN, [])
t.strictSame(OUTPUT, [
t.strictSame(npm._mockOutputs, [
[`\nEntering npm script environment at location:\n${process.cwd()}\nType 'exit' or ^D when finished\n`],
], 'printed message about interactive shell')
t.end()
Expand All @@ -270,7 +268,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {

run(t, true, () => {
t.strictSame(LOG_WARN, [])
t.strictSame(OUTPUT, [
t.strictSame(npm._mockOutputs, [
[`\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`],
], 'printed message about interactive shell')
t.end()
Expand All @@ -282,7 +280,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
process.stdin.isTTY = false
run(t, true, () => {
t.strictSame(LOG_WARN, [])
t.strictSame(OUTPUT, [], 'no message about interactive shell')
t.strictSame(npm._mockOutputs, [], 'no message about interactive shell')
t.end()
})
})
Expand All @@ -294,7 +292,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
t.strictSame(LOG_WARN, [
['exec', 'Interactive mode disabled in CI environment'],
])
t.strictSame(OUTPUT, [], 'no message about interactive shell')
t.strictSame(npm._mockOutputs, [], 'no message about interactive shell')
t.end()
})
})
Expand All @@ -316,7 +314,7 @@ t.test('npm exec <noargs>, run interactive shell', t => {
ARB_CTOR.length = 0
MKDIRPS.length = 0
ARB_REIFY.length = 0
OUTPUT.length = 0
npm._mockOutputs.length = 0
RUN_SCRIPTS.length = 0
t.end()
})
Expand Down Expand Up @@ -1195,22 +1193,22 @@ t.test('workspaces', t => {
return rej(er)

t.strictSame(LOG_WARN, [])
t.strictSame(OUTPUT, [
t.strictSame(npm._mockOutputs, [
[`\nEntering npm script environment in workspace a@1.0.0 at location:\n${resolve(npm.localPrefix, 'packages/a')}\nType 'exit' or ^D when finished\n`],
], 'printed message about interactive shell')
res()
})
})

config.color = true
OUTPUT.length = 0
npm._mockOutputs.length = 0
await new Promise((res, rej) => {
exec.execWorkspaces([], ['a'], er => {
if (er)
return rej(er)

t.strictSame(LOG_WARN, [])
t.strictSame(OUTPUT, [
t.strictSame(npm._mockOutputs, [
[`\u001b[0m\u001b[0m\n\u001b[0mEntering npm script environment\u001b[0m\u001b[0m in workspace \u001b[32ma@1.0.0\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`],
], 'printed message about interactive shell')
res()
Expand Down
20 changes: 12 additions & 8 deletions test/lib/init.js
Expand Up @@ -3,7 +3,6 @@ const { resolve } = require('path')
const t = require('tap')
const mockNpm = require('../fixtures/mock-npm')

let result = ''
const npmLog = {
disableProgress: () => null,
enableProgress: () => null,
Expand All @@ -19,9 +18,6 @@ const config = {
const npm = mockNpm({
config,
log: npmLog,
output: (...msg) => {
result += msg.join('\n')
},
})
const mocks = {
'../../lib/utils/usage.js': () => 'usage instructions',
Expand All @@ -33,7 +29,6 @@ const _consolelog = console.log
const noop = () => {}

t.afterEach(() => {
result = ''
config.yes = true
config.package = undefined
npm.log = npmLog
Expand Down Expand Up @@ -322,6 +317,9 @@ t.test('npm init error', t => {

t.test('workspaces', t => {
t.test('no args', t => {
t.teardown(() => {
npm._mockOutputs.length = 0
})
npm.localPrefix = t.testdir({
'package.json': JSON.stringify({
name: 'top-level',
Expand All @@ -340,12 +338,15 @@ t.test('workspaces', t => {
if (err)
throw err

t.matchSnapshot(result, 'should print helper info')
t.matchSnapshot(npm._mockOutputs, 'should print helper info')
t.end()
})
})

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

t.matchSnapshot(result, 'should print helper info')
t.matchSnapshot(npm._mockOutputs, 'should print helper info')
t.end()
})
})

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

t.matchSnapshot(result, 'should print helper info')
t.matchSnapshot(npm._mockOutputs, 'should print helper info')
t.end()
})
})
Expand Down