Skip to content

Commit 74b2159

Browse files
test(editor): use a node script as an external editor MONGOSH-992 (#1118)
* test(editor): use a node script as an external editor MONGOSH-992 * refactor: rename a temp file * refactor: rename a variable * refactor: remove extra dir creation * refactor: address pr comments * test: add some e2e tests * teat: remove before hook from the runEditCommand context * test: move editor e2e tests to a separate module * refactor: use stringify * refactor: move destructing to describe * feat: revert destructing moving commit * refactor: use regex to remove first word * test: stringify editor name * test: create nyc_output to fix tests of windows * test: fix empty editor call * refactor: replace two regex with one * test: try to kill process with taskkill * test: double quotes * test: process.exec * test: require child_process * test: use declared identifier child_process * test: childProcess * test: execSync * test: showStackTraces * refactor: replace cross-spawn with pawn
1 parent 4a4844e commit 74b2159

File tree

8 files changed

+312
-40
lines changed

8 files changed

+312
-40
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { expect } from 'chai';
2+
import path from 'path';
3+
import { promises as fs } from 'fs';
4+
import { promisify } from 'util';
5+
import rimraf from 'rimraf';
6+
7+
import { eventually } from '../../../testing/eventually';
8+
import { useTmpdir, fakeExternalEditor, setTemporaryHomeDirectory } from './repl-helpers';
9+
import { TestShell } from './test-shell';
10+
11+
describe('external editor e2e', () => {
12+
const tmpdir = useTmpdir();
13+
let homedir: string;
14+
let env: Record<string, string>;
15+
let shell: TestShell;
16+
17+
beforeEach(async() => {
18+
const homeInfo = setTemporaryHomeDirectory();
19+
20+
homedir = homeInfo.homedir;
21+
env = homeInfo.env;
22+
23+
shell = TestShell.start({
24+
args: [ '--nodb' ],
25+
forceTerminal: true,
26+
env
27+
});
28+
29+
await shell.waitForPrompt();
30+
shell.assertNoErrors();
31+
32+
// make nyc happy when spawning npm below
33+
await fs.mkdir(path.join(tmpdir.path, '.mongodb', '.nyc_output', 'processinfo'), { recursive: true });
34+
await fs.mkdir(path.join(tmpdir.path, 'mongodb', '.nyc_output', 'processinfo'), { recursive: true });
35+
});
36+
37+
afterEach(async() => {
38+
await TestShell.killall.call(this);
39+
try {
40+
await promisify(rimraf)(homedir);
41+
} catch (err) {
42+
// On Windows in CI, this can fail with EPERM for some reason.
43+
// If it does, just log the error instead of failing all tests.
44+
console.error('Could not remove fake home directory:', err);
45+
}
46+
});
47+
48+
it('returns a modified identifier for fn', async() => {
49+
const shellOriginalInput = "const fn = function () { console.log(111); }; edit('fn')";
50+
const editorOutput = `function () {
51+
console.log(222);
52+
};`;
53+
const shellModifiedInput = 'fn = function () { console.log(222); };';
54+
const editor = await fakeExternalEditor(editorOutput);
55+
const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`);
56+
57+
expect(result).to.include('"editor" has been changed');
58+
shell.writeInputLine(shellOriginalInput);
59+
await eventually(() => {
60+
shell.assertContainsOutput(shellModifiedInput);
61+
});
62+
});
63+
64+
it('returns a modified identifier for var', async() => {
65+
const shellOriginalInput = "const myVar = '111'; edit('myVar')";
66+
const editorOutput = "const myVar = '222';";
67+
const shellModifiedInput = "myVar = '222';";
68+
const editor = await fakeExternalEditor(editorOutput);
69+
const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`);
70+
71+
expect(result).to.include('"editor" has been changed');
72+
shell.writeInputLine(shellOriginalInput);
73+
await eventually(() => {
74+
shell.assertContainsOutput(shellModifiedInput);
75+
});
76+
});
77+
78+
it('returns a modified identifier for a.b.c', async() => {
79+
const shellOriginalInput = "const myObj = { field: { child: 'string value' } }; edit('myObj')";
80+
const editorOutput = `const myObj = {
81+
field: {
82+
child: 'new value'
83+
}
84+
};`;
85+
const shellModifiedInput = "myObj = { field: { child: 'new value' } };";
86+
const editor = await fakeExternalEditor(editorOutput);
87+
const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`);
88+
89+
expect(result).to.include('"editor" has been changed');
90+
shell.writeInputLine(shellOriginalInput);
91+
await eventually(() => {
92+
shell.assertContainsOutput(shellModifiedInput);
93+
});
94+
});
95+
96+
it('returns an error when editor exits with exitCode 1', async() => {
97+
const shellOriginalInput = 'edit function() {}';
98+
const editor = await fakeExternalEditor();
99+
const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`);
100+
await shell.executeLine('config.set("showStackTraces", true); print(process.env.PATH);');
101+
102+
expect(result).to.include('"editor" has been changed');
103+
shell.writeInputLine(shellOriginalInput);
104+
await eventually(() => {
105+
shell.assertContainsError('failed with an exit code 1');
106+
});
107+
});
108+
109+
it('opens an empty editor', async() => {
110+
const output = '';
111+
const editor = await fakeExternalEditor(output);
112+
const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`);
113+
114+
expect(result).to.include('"editor" has been changed');
115+
shell.writeInputLine('edit');
116+
await eventually(() => {
117+
shell.assertContainsOutput(output);
118+
});
119+
});
120+
});

packages/cli-repl/test/e2e.spec.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { promisify } from 'util';
99
import rimraf from 'rimraf';
1010
import path from 'path';
1111
import os from 'os';
12-
import { readReplLogfile } from './repl-helpers';
12+
import { readReplLogfile, setTemporaryHomeDirectory } from './repl-helpers';
1313
import { bson } from '@mongosh/service-provider-core';
1414
const { EJSON } = bson;
1515

@@ -145,6 +145,7 @@ describe('e2e', function() {
145145
expect(result).to.match(/a<A>/);
146146
});
147147
});
148+
148149
describe('set db', () => {
149150
for (const { mode, dbname, dbnameUri } of [
150151
{ mode: 'no special characetrs', dbname: 'testdb1', dbnameUri: 'testdb1' },
@@ -773,26 +774,24 @@ describe('e2e', function() {
773774
});
774775

775776
describe('config, logging and rc file', () => {
776-
let shell: TestShell;
777777
let homedir: string;
778+
let env: Record<string, string>;
779+
let shell: TestShell;
778780
let configPath: string;
779781
let logBasePath: string;
780782
let logPath: string;
781783
let historyPath: string;
782784
let readConfig: () => Promise<any>;
783785
let readLogfile: () => Promise<any[]>;
784786
let startTestShell: (...extraArgs: string[]) => Promise<TestShell>;
785-
let env: Record<string, string>;
786787

787788
beforeEach(() => {
788-
homedir = path.resolve(
789-
__dirname, '..', '..', '..', 'tmp', `cli-repl-home-${Date.now()}-${Math.random()}`);
790-
env = {
791-
...process.env, HOME: homedir, USERPROFILE: homedir
792-
};
789+
const homeInfo = setTemporaryHomeDirectory();
790+
791+
homedir = homeInfo.homedir;
792+
env = homeInfo.env;
793+
793794
if (process.platform === 'win32') {
794-
env.LOCALAPPDATA = path.join(homedir, 'local');
795-
env.APPDATA = path.join(homedir, 'roaming');
796795
logBasePath = path.resolve(homedir, 'local', 'mongodb', 'mongosh');
797796
configPath = path.resolve(homedir, 'roaming', 'mongodb', 'mongosh', 'config');
798797
historyPath = path.resolve(homedir, 'roaming', 'mongodb', 'mongosh', 'mongosh_repl_history');

packages/cli-repl/test/repl-helpers.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,40 @@ async function readReplLogfile(logPath: string) {
7676
.map((line) => JSON.parse(line));
7777
}
7878

79+
80+
const fakeExternalEditor = async(output?: string) => {
81+
const base = path.resolve(__dirname, '..', '..', '..', 'tmp', 'test', `${Date.now()}`, `${Math.random()}`);
82+
const tmpDoc = path.join(base, 'editor-script.js');
83+
let script: string;
84+
85+
if (typeof output === 'string') {
86+
script = `(async () => {
87+
const tmpDoc = process.argv[process.argv.length - 1];
88+
const { promises: { writeFile } } = require("fs");
89+
await writeFile(tmpDoc, ${JSON.stringify(output)}, { mode: 0o600 });
90+
})()`;
91+
} else {
92+
script = 'process.exit(1);';
93+
}
94+
95+
await fs.mkdir(path.dirname(tmpDoc), { recursive: true, mode: 0o700 });
96+
await fs.writeFile(tmpDoc, script, { mode: 0o600 });
97+
98+
return `node ${tmpDoc}`;
99+
};
100+
101+
const setTemporaryHomeDirectory = () => {
102+
const homedir: string = path.resolve(__dirname, '..', '..', '..', 'tmp', `cli-repl-home-${Date.now()}-${Math.random()}`);
103+
const env: Record<string, string> = { ...process.env, HOME: homedir, USERPROFILE: homedir };
104+
105+
if (process.platform === 'win32') {
106+
env.LOCALAPPDATA = path.join(homedir, 'local');
107+
env.APPDATA = path.join(homedir, 'roaming');
108+
}
109+
110+
return { homedir, env };
111+
};
112+
79113
export {
80114
expect,
81115
sinon,
@@ -85,5 +119,7 @@ export {
85119
waitEval,
86120
waitCompletion,
87121
fakeTTYProps,
88-
readReplLogfile
122+
readReplLogfile,
123+
fakeExternalEditor,
124+
setTemporaryHomeDirectory
89125
};

packages/editor/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,9 @@
3434
"@mongosh/shell-api": "0.0.0-dev.0",
3535
"@mongosh/shell-evaluator": "0.0.0-dev.0",
3636
"@mongosh/types": "0.0.0-dev.0",
37-
"cross-spawn": "^7.0.3",
3837
"js-beautify": "^1.14.0"
3938
},
4039
"devDependencies": {
41-
"@types/cross-spawn": "^6.0.2",
4240
"nanobus": "^4.5.0"
4341
}
4442
}

0 commit comments

Comments
 (0)