-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(cli-repl): do not run prompt and initial input in parallel MONGOS…
…H-1617 (#1856) Users were getting incorrect behavior when: 1. A custom asynchronous prompt was set outside of the main REPL input, e.g. in a `.mongoshrc.js` file or a `--eval` script, and 2. The main REPL input was readable before we started evaluating the prompt. because then, the first lines of input and the prompt may have ended up executing in parallel. Setting the prompt before starting to process input should resolve this issue.
- Loading branch information
Showing
4 changed files
with
121 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,11 @@ import MongoshNodeRepl from './mongosh-repl'; | |
import { parseAnyLogEntry } from '../../shell-api/src/log-entry'; | ||
import stripAnsi from 'strip-ansi'; | ||
|
||
function nonnull<T>(value: T | null | undefined): NonNullable<T> { | ||
if (!value) throw new Error(); | ||
return value; | ||
} | ||
|
||
const delay = promisify(setTimeout); | ||
|
||
const multilineCode = `(function() { | ||
|
@@ -280,7 +285,7 @@ describe('MongoshNodeRepl', function () { | |
it('handles a long series of errors', async function () { | ||
input.write('-asdf();\n'.repeat(20)); | ||
await waitEval(bus); | ||
expect(mongoshRepl.runtimeState().repl.listenerCount('SIGINT')).to.equal( | ||
expect(mongoshRepl.runtimeState().repl?.listenerCount('SIGINT')).to.equal( | ||
1 | ||
); | ||
}); | ||
|
@@ -548,7 +553,7 @@ describe('MongoshNodeRepl', function () { | |
await tick(); | ||
input.write('"bar" })\n'); | ||
await tick(); | ||
expect(mongoshRepl.runtimeState().repl.context.obj).to.deep.equal({ | ||
expect(mongoshRepl.runtimeState().context.obj).to.deep.equal({ | ||
foo: 'bar', | ||
}); | ||
expect(output).not.to.include('obj = ({ foo: "bar" })'); | ||
|
@@ -571,7 +576,7 @@ describe('MongoshNodeRepl', function () { | |
await tick(); | ||
input.write('\u0004'); // Ctrl+D | ||
await tick(); | ||
expect(mongoshRepl.runtimeState().repl.context.obj).to.deep.equal({ | ||
expect(mongoshRepl.runtimeState().context.obj).to.deep.equal({ | ||
foo: 'baz', | ||
}); | ||
expect(output).not.to.include('obj = ({ foo: "baz" })'); | ||
|
@@ -596,7 +601,7 @@ describe('MongoshNodeRepl', function () { | |
await tick(); | ||
input.write('"bar" })\n'); | ||
await tick(); | ||
expect(mongoshRepl.runtimeState().repl.context.obj).to.deep.equal({ | ||
expect(mongoshRepl.runtimeState().context.obj).to.deep.equal({ | ||
foo: 'bar', | ||
}); | ||
expect(output).not.to.include('obj = ({ foo: "bar" })'); | ||
|
@@ -638,19 +643,31 @@ describe('MongoshNodeRepl', function () { | |
|
||
it('does not crash if hitting enter and then up', async function () { | ||
input.write('\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
input.write(`${arrowUp}`); | ||
await tick(); | ||
}); | ||
|
||
context('redaction', function () { | ||
it('removes sensitive commands by default', async function () { | ||
input.write('connect\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
input.write('connection\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
input.write('db.test.insert({ email: "[email protected]" })\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
|
||
expect(getHistory()).to.deep.equal([ | ||
'db.test.insert({ email: "[email protected]" })', | ||
|
@@ -662,11 +679,20 @@ describe('MongoshNodeRepl', function () { | |
input.write('config.set("redactHistory", "keep");\n'); | ||
await tick(); | ||
input.write('connect\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
input.write('connection\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
input.write('db.test.insert({ email: "[email protected]" })\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
|
||
expect(getHistory()).to.deep.equal([ | ||
'db.test.insert({ email: "[email protected]" })', | ||
|
@@ -680,11 +706,20 @@ describe('MongoshNodeRepl', function () { | |
input.write('config.set("redactHistory", "remove-redact");\n'); | ||
await tick(); | ||
input.write('connect\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
input.write('connection\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
input.write('db.test.insert({ email: "[email protected]" })\n'); | ||
await once(mongoshRepl.runtimeState().repl, 'flushHistory'); | ||
await once( | ||
nonnull(mongoshRepl.runtimeState().repl), | ||
'flushHistory' | ||
); | ||
|
||
expect(getHistory()).to.deep.equal([ | ||
'db.test.insert({ email: "<email>" })', | ||
|
@@ -783,11 +818,9 @@ describe('MongoshNodeRepl', function () { | |
|
||
it('does not refresh the prompt if a window resize occurs while evaluating', async function () { | ||
let resolveInProgress; | ||
mongoshRepl.runtimeState().repl.context.inProgress = new Promise( | ||
(resolve) => { | ||
resolveInProgress = resolve; | ||
} | ||
); | ||
mongoshRepl.runtimeState().context.inProgress = new Promise((resolve) => { | ||
resolveInProgress = resolve; | ||
}); | ||
input.write('inProgress\n'); | ||
await tick(); | ||
|
||
|
@@ -887,7 +920,7 @@ describe('MongoshNodeRepl', function () { | |
context('user prompts', function () { | ||
beforeEach(function () { | ||
// No boolean equivalent for 'passwordPrompt' in the API, so provide one: | ||
mongoshRepl.runtimeState().repl.context.booleanPrompt = (question) => { | ||
mongoshRepl.runtimeState().context.booleanPrompt = (question) => { | ||
return Object.assign(mongoshRepl.onPrompt(question, 'yesno'), { | ||
[Symbol.for('@@mongosh.syntheticPromise')]: true, | ||
}); | ||
|
@@ -1283,6 +1316,29 @@ describe('MongoshNodeRepl', function () { | |
expect(output).to.contain('> '); | ||
}); | ||
}); | ||
|
||
context('pre-specified user-provided prompt', function () { | ||
it('does not attempt to run the prompt in parallel with initial input', async function () { | ||
output = ''; | ||
const initialized = await mongoshRepl.initialize(serviceProvider); | ||
await mongoshRepl.loadExternalCode( | ||
`prompt = () => { | ||
if (globalThis.isEvaluating) print('FAILED -- Parallel execution detected!'); | ||
globalThis.isEvaluating = true; | ||
sleep(100); | ||
globalThis.isEvaluating = false; | ||
return '> '; | ||
};`, | ||
'test' | ||
); | ||
expect(mongoshRepl.runtimeState().context.prompt).to.be.a('function'); | ||
// Queue up input *before* starting the REPL itself | ||
input.write('prompt()\n'); | ||
await mongoshRepl.startRepl(initialized); | ||
await waitEval(bus); | ||
expect(output).not.to.include('FAILED'); | ||
}); | ||
}); | ||
}); | ||
|
||
context('before the REPL starts', function () { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters