vscode-cosmosdb/test/mongoShell.test.ts

306 строки
11 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// CONSIDER: Run in pipeline
import assert from 'assert';
import * as cp from 'child_process';
import * as fse from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { isNumber } from 'util';
import type * as vscode from 'vscode';
import { ext, isWindows, MongoShell, parseError, type IDisposable } from '../extension.bundle';
import { runWithSetting } from './runWithSetting';
import { setEnvironmentVariables } from './util/setEnvironmentVariables';
const VERBOSE = false; // If true, the output from the Mongo server and shell will be sent to the console for debugging purposes
let testsSupported: boolean = true;
if (!isWindows) {
// CONSIDER: Non-Windows
console.warn(`Not running in Windows - skipping MongoShell tests`);
testsSupported = false;
}
suite('MongoShell', async function (this: Mocha.Suite): Promise<void> {
// https://github.com/mochajs/mocha/issues/2025
this.timeout(10000);
async function testIfSupported(title: string, fn?: Mocha.Func | Mocha.AsyncFunc): Promise<void> {
await runWithSetting(ext.settingsKeys.mongoShellTimeout, '60', async () => {
if (testsSupported) {
test(title, fn);
} else {
test(title);
}
});
}
// CONSIDER: Make more generic
let mongodCP: cp.ChildProcess;
const mongodPath = 'c:\\Program Files\\MongoDB\\Server\\4.2\\bin\\mongod.exe';
const mongoPath = 'c:\\Program Files\\MongoDB\\Server\\4.2\\bin\\mongo.exe';
let mongoDOutput = '';
let mongoDErrors = '';
let isClosed = false;
if (!fse.existsSync(mongodPath)) {
console.log(`Couldn't find mongod.exe at ${mongodPath} - skipping MongoShell tests`);
testsSupported = false;
} else if (!fse.existsSync(mongodPath)) {
console.log(`Couldn't find mongo.exe at ${mongoPath} - skipping MongoShell tests`);
testsSupported = false;
} else {
// Prevent code 100 error: https://stackoverflow.com/questions/41420466/mongodb-shuts-down-with-code-100
await fse.ensureDir('D:\\data\\db\\');
}
class FakeOutputChannel implements vscode.OutputChannel {
public name: string;
public output: string;
public append(value: string): void {
assert(value !== undefined);
assert(!value.includes('undefined'));
this.output = this.output ? this.output + os.EOL + value : value;
log(value, 'Output channel: ');
}
public appendLine(value: string): void {
assert(value !== undefined);
this.append(value + os.EOL);
}
public clear(): void {}
public show(preserveFocus?: boolean): void;
public show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
public show(_column?: any, _preserveFocus?: any): void {}
public hide(): void {}
public dispose(): void {}
public replace(_value: string): void {}
}
function log(text: string, linePrefix: string): void {
text = text.replace(/(^|[\r\n]+)/g, '$1' + linePrefix);
if (VERBOSE) {
console.log(text);
}
}
async function delay(milliseconds: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
function executeInShell(command: string): string {
return cp.execSync(command, {}).toString();
}
suiteSetup(() => {
if (testsSupported) {
assert(fse.existsSync(mongodPath), "Couldn't find mongod.exe at " + mongodPath);
assert(fse.existsSync(mongoPath), "Couldn't find mongo.exe at " + mongoPath);
// Shut down any still-running mongo server
try {
executeInShell('taskkill /f /im mongod.exe');
} catch (error) {
assert(
/The process .* not found/.test(parseError(error).message),
`Error killing mongod: ${parseError(error).message}`,
);
}
mongodCP = cp.spawn(mongodPath, ['--quiet']);
mongodCP.stdout?.on('data', (buffer: Buffer) => {
log(buffer.toString(), 'mongo server: ');
mongoDOutput += buffer.toString();
});
mongodCP.stderr?.on('data', (buffer: Buffer) => {
log(buffer.toString(), 'mongo server STDERR: ');
mongoDErrors += buffer.toString();
});
mongodCP.on('error', (error: unknown) => {
log(parseError(error).message, 'mongo server Error: ');
mongoDErrors += parseError(error).message + os.EOL;
});
mongodCP.on('close', (code?: number) => {
console.log(`mongo server: Close code=${code}`);
isClosed = true;
if (isNumber(code) && code !== 0) {
mongoDErrors += `mongo server: Closed with code "${code}"${os.EOL}`;
}
});
}
});
suiteTeardown(() => {
if (mongodCP) {
mongodCP.kill();
}
});
await testIfSupported('Verify mongod running', async () => {
while (!mongoDOutput.includes('waiting for connections on port 27017')) {
assert.equal(mongoDErrors, '', 'Expected no errors');
assert(!isClosed);
await delay(50);
}
});
async function testShellCommand(options: {
script: string;
expectedResult?: string;
expectedError?: string | RegExp;
expectedOutput?: RegExp;
title?: string; // Defaults to script
args?: string[]; // Defaults to []
mongoPath?: string; // Defaults to the correct mongo path
env?: { [key: string]: string }; // Add to environment
timeoutSeconds?: number;
}): Promise<void> {
await testIfSupported(options.title || options.script, async () => {
assert(!isClosed);
assert(mongoDErrors === '');
let previousEnv: IDisposable | undefined;
let shell: MongoShell | undefined;
const outputChannel = new FakeOutputChannel();
try {
previousEnv = setEnvironmentVariables(options.env || {});
shell = await MongoShell.create(
options.mongoPath || mongoPath,
options.args || [],
'',
false,
outputChannel,
options.timeoutSeconds || 5,
);
const result = await shell.executeScript(options.script);
if (options.expectedError) {
assert(false, `Expected error did not occur: '${options.expectedError}'`);
}
if (options.expectedResult !== undefined) {
assert.equal(result, options.expectedResult);
}
} catch (error) {
const message = parseError(error).message;
if (options.expectedError instanceof RegExp) {
assert(
options.expectedError.test(message),
`Actual error did not match expected error regex. Actual error: ${message}`,
);
} else if (typeof options.expectedError === 'string') {
assert.equal(message, options.expectedError);
} else {
assert(false, `Unexpected error during the test: ${message}`);
}
if (options.expectedOutput instanceof RegExp) {
assert(
options.expectedOutput.test(outputChannel.output),
`Actual contents written to output channel did not match expected regex. Actual output channel contents: ${outputChannel.output}`,
);
}
} finally {
if (shell) {
shell.dispose();
}
if (previousEnv) {
previousEnv.dispose();
}
}
});
}
await testShellCommand({
script: 'use abc',
expectedResult: 'switched to db abc',
});
await testShellCommand({
title: 'Incorrect path',
script: 'use abc',
mongoPath: '/notfound/mongo.exe',
expectedError: /Could not find .*notfound.*mongo.exe/,
});
await testShellCommand({
title: 'Find mongo through PATH',
script: 'use abc',
mongoPath: 'mongo',
expectedResult: 'switched to db abc',
env: {
PATH: process.env.path! + ';' + path.dirname(mongoPath),
},
});
await testShellCommand({
title: 'With valid argument',
script: 'use abc',
args: ['--quiet'],
expectedResult: 'switched to db abc',
});
await testShellCommand({
title: 'With invalid argument',
script: '',
args: ['--hey-man-how-are-you'],
expectedError: /Error parsing command line: unrecognised option/,
});
await testShellCommand({
title: 'Output window may contain additional information',
script: '',
args: ['-u', 'baduser', '-p', 'badpassword'],
expectedError: /The output window may contain additional information/,
});
await testShellCommand({
title: 'With bad credentials',
script: '',
args: ['-u', 'baduser', '-p', 'badpassword'],
expectedError: /The process exited with code 1/,
expectedOutput: /Authentication failed/,
});
await testShellCommand({
title: 'Process exits immediately',
script: '',
args: ['--version'],
expectedError: /The process exited prematurely/,
});
await testShellCommand({
title: 'Javascript',
script: 'for (var i = 0; i < 123; ++i) { }; i',
expectedResult: '123',
});
await testShellCommand({
title: 'Actual timeout',
script: 'for (var i = 0; i < 10000000; ++i) { }; i',
expectedError:
/Timed out trying to execute the Mongo script. To use a longer timeout, modify the VS Code 'mongo.shell.timeout' setting./,
timeoutSeconds: 2,
});
await testIfSupported("More results than displayed (type 'it' for more -> (More))", async () => {
const shell = await MongoShell.create(mongoPath, [], '', false, new FakeOutputChannel(), 5);
await shell.executeScript('db.mongoShellTest.drop()');
await shell.executeScript('for (var i = 0; i < 50; ++i) { db.mongoShellTest.insert({a:i}); }');
const result = await shell.executeScript('db.mongoShellTest.find().pretty()');
assert(!result.includes('Type "it" for more'));
assert(result.includes('(More)'));
shell.dispose();
});
});