diff --git a/packages/cli-repl/src/mongosh-repl.spec.ts b/packages/cli-repl/src/mongosh-repl.spec.ts index 581ccf0fcd..6a84906a47 100644 --- a/packages/cli-repl/src/mongosh-repl.spec.ts +++ b/packages/cli-repl/src/mongosh-repl.spec.ts @@ -220,6 +220,25 @@ describe('MongoshNodeRepl', () => { await waitEval(bus); expect(output).to.include('ISODate("2021-05-04T15:49:33.000Z")'); }); + + 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(1); + }); + + it('does not run statements that should not run', async() => { + input.write(` + sleep(0); + throw new Error(); + if (false) + print ('!this should not run!'); + `); + for (let i = 0; i < 20; i++) { + await tick(); + } + expect(output).not.to.include('!this should not run!'); + }); }); context('with terminal: true', () => { diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index a104d2b115..ff3637b57d 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -9,8 +9,8 @@ import askpassword from 'askpassword'; import { Console } from 'console'; import { once } from 'events'; import prettyRepl from 'pretty-repl'; -import { ReplOptions, REPLServer } from 'repl'; -import type { Readable, Writable } from 'stream'; +import { ReplOptions, REPLServer, start as replStart } from 'repl'; +import { Readable, Writable, PassThrough } from 'stream'; import type { ReadStream, WriteStream } from 'tty'; import { callbackify, promisify } from 'util'; import * as asyncRepl from './async-repl'; @@ -56,6 +56,40 @@ type Mutable = { -readonly[P in keyof T]: T[P] }; +// https://github.com/nodejs/node/pull/38314 +function fixupReplForNodeBug38314(repl: REPLServer): void { + { + // Check whether bug is present: + const input = new PassThrough(); + const output = new PassThrough(); + const evalFn = (code: any, ctx: any, filename: any, cb: any) => cb(new Error('err')); + const prompt = 'prompt#'; + replStart({ input, output, eval: evalFn as any, prompt }); + input.end('s\n'); + if (!String(output.read()).includes('prompt#prompt#')) { + return; // All good, nothing to do here. + } + } + + // If it is, fix up the REPL's domain 'error' listener to not call displayPrompt() + const domain = (repl as any)._domain; + const domainErrorListeners = domain.listeners('error'); + const origListener = domainErrorListeners.find((fn: any) => fn.name === 'debugDomainError'); + if (!origListener) { + throw new Error('Could not find REPL domain error listener'); + } + domain.removeListener('error', origListener); + domain.on('error', function(this: any, err: Error) { + const origDisplayPrompt = repl.displayPrompt; + repl.displayPrompt = () => {}; + try { + origListener.call(this, err); + } finally { + repl.displayPrompt = origDisplayPrompt; + } + }); +} + /** * An instance of a `mongosh` REPL, without any of the actual I/O. */ @@ -113,6 +147,7 @@ class MongoshNodeRepl implements EvaluationListener { (err: Error) => Object.assign(new MongoshInternalError(err.message), { stack: err.stack }), ...this.nodeReplOptions }); + fixupReplForNodeBug38314(repl); const console = new Console({ stdout: this.output, diff --git a/packages/cli-repl/test/e2e.spec.ts b/packages/cli-repl/test/e2e.spec.ts index cd5c95cccb..755bfd0366 100644 --- a/packages/cli-repl/test/e2e.spec.ts +++ b/packages/cli-repl/test/e2e.spec.ts @@ -8,7 +8,7 @@ import { promises as fs, createReadStream } from 'fs'; import { promisify } from 'util'; import rimraf from 'rimraf'; import path from 'path'; -import { readReplLogfile, hasNodeBug38314 } from './repl-helpers'; +import { readReplLogfile } from './repl-helpers'; describe('e2e', function() { const testServer = startTestServer('shared'); @@ -558,8 +558,6 @@ describe('e2e', function() { let result; result = await shell.executeLine('require("a")'); expect(result).to.match(/Error: Cannot find module 'a'/); - // Wait for double prompt because of Node.js REPL bug - if (hasNodeBug38314()) await eventually(() => shell.assertContainsOutput('> > ')); result = await shell.executeLine('require("./a")'); expect(result).to.match(/^A$/m); result = await shell.executeLine('require("b")'); diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index 8d9bd0f5ff..be77d319cf 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -7,8 +7,6 @@ import chai, { expect } from 'chai'; import sinon from 'ts-sinon'; import sinonChai from 'sinon-chai'; import chaiAsPromised from 'chai-as-promised'; -import repl from 'repl'; -import { PassThrough } from 'stream'; import type { MongoshBus, MongoshBusEventsMap } from '@mongosh/types'; chai.use(sinonChai); @@ -78,17 +76,6 @@ async function readReplLogfile(logPath: string) { .map((line) => JSON.parse(line)); } -// https://github.com/nodejs/node/pull/38314 -function hasNodeBug38314() { - const input = new PassThrough(); - const output = new PassThrough(); - const evalFn = (code, ctx, filename, cb) => cb(new Error('err')); - const prompt = 'prompt#'; - repl.start({ input, output, eval: evalFn, prompt }); - input.end('s\n'); - return String(output.read()).includes('prompt#prompt#'); -} - export { expect, sinon, @@ -98,6 +85,5 @@ export { waitEval, waitCompletion, fakeTTYProps, - readReplLogfile, - hasNodeBug38314 + readReplLogfile };