From 0bd048ac1ea8eab37c52af859d25f64d75b63b14 Mon Sep 17 00:00:00 2001 From: nodkz Date: Wed, 7 Jun 2017 22:20:55 +0600 Subject: [PATCH] feat: Reimplement mongo-prebuild. Change config options. BREAKING CHANGES --- .eslintrc | 5 + .flowconfig | 36 ++++- README.md | 23 +++ package.json | 4 +- src/MongoMemoryServer.js | 192 ++++++++++++++++++++++ src/__tests__/multipleDB-test.js | 2 +- src/__tests__/singleDB-test.js | 2 +- src/index.js | 193 +---------------------- src/util/MongoBinary.js | 76 +++++++++ src/util/MongoInstance.js | 145 +++++++++++++++++ src/util/__tests__/MongoBinary-test.js | 35 ++++ src/util/__tests__/MongoInstance-test.js | 63 ++++++++ src/util/mongo_killer.js | 33 ++++ yarn.lock | 47 ++---- 14 files changed, 629 insertions(+), 227 deletions(-) create mode 100644 src/MongoMemoryServer.js create mode 100644 src/util/MongoBinary.js create mode 100644 src/util/MongoInstance.js create mode 100644 src/util/__tests__/MongoBinary-test.js create mode 100644 src/util/__tests__/MongoInstance-test.js create mode 100644 src/util/mongo_killer.js diff --git a/.eslintrc b/.eslintrc index bd91a0554..3151a302f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -29,6 +29,11 @@ "jasmine": true, "jest": true }, + "globals": { + "Class": true, + "Iterator": true, + "$Shape": true, + }, "plugins": [ "flowtype", "prettier" diff --git a/.flowconfig b/.flowconfig index fd9e98c1e..65e5542fa 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,11 +1,43 @@ [ignore] -.*/lib/.* -.*/src/.* +.*/coverage/.* +/lib/.* +/dist/.* +.*/node_modules/ajv.* +.*/node_modules/acorn.* +.*/node_modules/async.* +.*/node_modules/babel.* +.*/node_modules/bluebird.* +.*/node_modules/caniuse-db.* .*/node_modules/config-chain.* +.*/node_modules/conventional-changelog.* +.*/node_modules/core-js.* +.*/node_modules/cssstyle.* +.*/node_modules/diff.* +.*/node_modules/es5-ext.* +.*/node_modules/escope.* +.*/node_modules/escodegen.* +.*/node_modules/eslint.* +.*/node_modules/github.* +.*/node_modules/fsevents.* +.*/node_modules/jsdoctypeparser.* +.*/node_modules/jsdom.* +.*/node_modules/iconv.* +.*/node_modules/istanbul.* +.*/node_modules/handlebars.* +.*/node_modules/markdown.* +.*/node_modules/node-notifier.* .*/node_modules/npmconf.* +.*/node_modules/prettier.* +.*/node_modules/source-map.* +.*/node_modules/travis.* +.*/node_modules/uglify.* +.*/node_modules/yargs.* [include] [libs] [options] +esproposal.class_instance_fields=enable +suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe +unsafe.enable_getters_and_setters=true diff --git a/README.md b/README.md index 4b444b89b..0dddde220 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,29 @@ mongod.stop(); // or it will be stopped automatically when you exit from script ``` +### Available options +All options are optional. +```js +const mongod = new MongodbMemoryServer({ + instance: { + port?: ?number, // by default choose any free port + dbPath?: string, // by default create in temp directory + storageEngine?: string, // by default `ephemeralForTest` + debug?: boolean, // by default false + }, + binary: { + version?: string, // by default '3.4.4' + downloadDir?: string, // by default %HOME/.mongodb-prebuilt + platform?: string, // by default os.platform() + arch?: string, // by default os.arch() + http?: any, // see mongodb-download package + debug?: boolean, // by default false + }, + debug?: boolean, // by default false + autoStart?: boolean, // by default true +}); +``` + ### Provide connection string to mongoose ```js import mongoose from 'mongoose'; diff --git a/package.json b/package.json index 71d9cbe88..e1c247f8b 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,10 @@ "semantic-release": "^6.3.6" }, "dependencies": { + "debug": "^2.6.8", "get-port": "^3.1.0", - "mongodb-prebuilt": "^6.3.3", + "glob": "^7.1.2", + "mongodb-download": "^2.2.3", "tmp": "^0.0.31", "uuid": "^3.0.1" }, diff --git a/src/MongoMemoryServer.js b/src/MongoMemoryServer.js new file mode 100644 index 000000000..56bf9226c --- /dev/null +++ b/src/MongoMemoryServer.js @@ -0,0 +1,192 @@ +/* @flow */ + +import type { ChildProcess } from 'child_process'; +import uuid from 'uuid/v4'; +import tmp from 'tmp'; +import getport from 'get-port'; +import MongoInstance from './util/MongoInstance'; + +tmp.setGracefulCleanup(); + +export type MongoMemoryServerOptsT = { + instance: { + port?: ?number, + dbPath?: string, + storageEngine?: string, + debug?: boolean, + }, + binary: { + version?: string, + downloadDir?: string, + platform?: string, + arch?: string, + http?: any, + debug?: boolean, + }, + debug?: boolean, + spawn: any, + autoStart?: boolean, +}; + +export type MongoInstanceDataT = { + port: number, + dbPath: string, + uri: string, + storageEngine: string, + mongod: ChildProcess, + tmpDir?: { + name: string, + removeCallback: Function, + }, +}; + +async function generateConnectionString(port: number, dbName: ?string): Promise { + const db = dbName || (await uuid()); + return `mongodb://localhost:${port}/${db}`; +} + +export default class MongoMemoryServer { + isRunning: boolean = false; + runningInstance: ?Promise; + opts: MongoMemoryServerOptsT; + + constructor(opts?: $Shape = {}) { + this.opts = opts; + if (!this.opts.instance) this.opts.instance = {}; + if (!this.opts.binary) this.opts.binary = {}; + + // autoStart by default + if (!opts.hasOwnProperty('autoStart') || opts.autoStart) { + if (opts.debug) { + console.log('Autostarting MongoDB instance...'); + } + this.start(); + } + } + + debug(msg: string) { + if (this.opts.debug) { + console.log(msg); + } + } + + async start(): Promise { + if (this.runningInstance) { + throw new Error( + 'MongoDB instance already in status startup/running/error. Use opts.debug = true for more info.' + ); + } + + this.runningInstance = this._startUpInstance() + .catch(err => { + if (err.message === 'Mongod shutting down' || err === 'Mongod shutting down') { + this.debug(`Mongodb does not started. Trying to start on another port one more time...`); + this.opts.instance.port = null; + return this._startUpInstance(); + } + throw err; + }) + .catch(err => { + if (!this.opts.debug) { + throw new Error( + `${err.message}\n\nUse debug option for more info: new MongoMemoryServer({ debug: true })` + ); + } + throw err; + }); + + return this.runningInstance.then(() => true); + } + + async _startUpInstance(): Promise { + const data = {}; + let tmpDir; + + const instOpts = this.opts.instance; + data.port = await getport(instOpts.port); + data.uri = await generateConnectionString(data.port); + data.storageEngine = instOpts.storageEngine || 'ephemeralForTest'; + if (instOpts.dbPath) { + data.dbPath = instOpts.dbPath; + } else { + tmpDir = tmp.dirSync({ prefix: 'mongo-mem-', unsafeCleanup: true }); + data.dbPath = tmpDir.name; + } + + this.debug(`Starting MongoDB instance with following options: ${JSON.stringify(data)}`); + + // Download if not exists mongo binaries in ~/.mongodb-prebuilt + // After that startup MongoDB instance + const mongod = await MongoInstance.run({ + instance: { + port: data.port, + storageEngine: data.storageEngine, + dbPath: data.dbPath, + debug: this.opts.instance.debug, + }, + binary: this.opts.binary, + spawn: this.opts.spawn, + debug: this.opts.debug, + }); + data.mongod = mongod; + data.tmpDir = tmpDir; + + return data; + } + + async stop(): Promise { + const { mongod, port, tmpDir } = (await this.getInstanceData(): MongoInstanceDataT); + + if (mongod && mongod.kill) { + this.debug(`Shutdown MongoDB server on port ${port} with pid ${mongod.pid}`); + mongod.kill(); + } + + if (tmpDir) { + this.debug(`Removing tmpDir ${tmpDir.name}`); + tmpDir.removeCallback(); + } + + this.runningInstance = null; + return true; + } + + async getInstanceData(): Promise { + if (this.runningInstance) { + return this.runningInstance; + } + throw new Error( + 'Database instance is not running. You should start database by calling start() method. BTW it should start automatically if opts.autoStart!=false. Also you may provide opts.debug=true for more info.' + ); + } + + async getUri(otherDbName?: string | boolean = false): Promise { + const { uri, port } = (await this.getInstanceData(): MongoInstanceDataT); + + // IF true OR string + if (otherDbName) { + if (typeof otherDbName === 'string') { + // generate uri with provided DB name on existed DB instance + return generateConnectionString(port, otherDbName); + } + // generate new random db name + return generateConnectionString(port); + } + + return uri; + } + + async getConnectionString(otherDbName: string | boolean = false): Promise { + return this.getUri(otherDbName); + } + + async getPort(): Promise { + const { port } = (await this.getInstanceData(): MongoInstanceDataT); + return port; + } + + async getDbPath(): Promise { + const { dbPath } = (await this.getInstanceData(): MongoInstanceDataT); + return dbPath; + } +} diff --git a/src/__tests__/multipleDB-test.js b/src/__tests__/multipleDB-test.js index c1fe0f723..96c3d51bd 100644 --- a/src/__tests__/multipleDB-test.js +++ b/src/__tests__/multipleDB-test.js @@ -1,7 +1,7 @@ /* @flow */ -import MongoDBMemoryServer from '../index'; import { MongoClient } from 'mongodb'; +import MongoDBMemoryServer from '../MongoMemoryServer'; jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000; diff --git a/src/__tests__/singleDB-test.js b/src/__tests__/singleDB-test.js index fb7ee9e3f..f77e5508f 100644 --- a/src/__tests__/singleDB-test.js +++ b/src/__tests__/singleDB-test.js @@ -1,7 +1,7 @@ /* @flow */ -import MongoDBMemoryServer from '../index'; import { MongoClient } from 'mongodb'; +import MongoDBMemoryServer from '../MongoMemoryServer'; jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000; diff --git a/src/index.js b/src/index.js index 52f6e889c..a6f7d8813 100644 --- a/src/index.js +++ b/src/index.js @@ -1,189 +1,6 @@ -/* @flow */ +import MongoMemoryServer from './MongoMemoryServer'; +import MongoInstance from './util/MongoInstance'; +import MongoBinary from './util/MongoBinary'; -import { MongodHelper } from 'mongodb-prebuilt'; -import uuid from 'uuid/v4'; -import tmp from 'tmp'; -import getport from 'get-port'; - -tmp.setGracefulCleanup(); - -export type MongoMemoryServerOptsT = { - port?: ?number, - storageEngine?: string, - dbPath?: string, - autoStart?: boolean, - debug?: boolean, -}; - -export type MongoInstanceDataT = { - port: number, - dbPath: string, - uri: string, - storageEngine: string, - mongodCli: MongodHelper, - tmpDir?: { - name: string, - removeCallback: Function, - }, -}; - -async function generateConnectionString(port: number, dbName: ?string): Promise { - const db = dbName || (await uuid()); - return `mongodb://localhost:${port}/${db}`; -} - -export default class MongoDBMemoryServer { - static mongodHelperStartup: ?Promise; - isRunning: boolean = false; - runningInstance: ?Promise; - opts: MongoMemoryServerOptsT; - - constructor(opts?: MongoMemoryServerOptsT = {}) { - this.opts = opts; - - // autoStart by default - if (!opts.hasOwnProperty('autoStart') || opts.autoStart) { - if (opts.debug) { - console.log('Autostarting MongoDB instance...'); - } - this.start(); - } - } - - debug(msg: string) { - if (this.opts.debug) { - console.log(msg); - } - } - - async start(): Promise { - if (this.runningInstance) { - throw new Error( - 'MongoDB instance already in status startup/running/error. Use opts.debug = true for more info.' - ); - } - - this.runningInstance = this._startUpInstance() - .catch(err => { - if (err.message === 'Mongod shutting down' || err === 'Mongod shutting down') { - this.debug(`Mongodb does not started. Trying to start on another port one more time...`); - this.opts.port = null; - return this._startUpInstance(); - } - throw err; - }) - .catch(err => { - if (!this.opts.debug) { - throw new Error( - `${err.message}\n\nUse debug option for more info: new MongoMemoryServer({ debug: true })` - ); - } - throw err; - }); - - return this.runningInstance.then(() => true); - } - - async _startUpInstance(): Promise { - const data = {}; - let tmpDir; - - data.port = await getport(this.opts.port); - data.uri = await generateConnectionString(data.port); - data.storageEngine = this.opts.storageEngine || 'ephemeralForTest'; - if (this.opts.dbPath) { - data.dbPath = this.opts.dbPath; - } else { - tmpDir = tmp.dirSync({ prefix: 'mongo-mem-', unsafeCleanup: true }); - data.dbPath = tmpDir.name; - } - - this.debug(`Starting MongoDB instance with following options: ${JSON.stringify(data)}`); - - const mongodCli = new MongodHelper([ - '--port', - data.port, - '--storageEngine', - data.storageEngine, - '--dbpath', - data.dbPath, - '--noauth', - ]); - - mongodCli.debug.enabled = this.opts.debug; - - if (this.constructor.mongodHelperStartup) { - await this.constructor.mongodHelperStartup; - } - - // Download if not exists mongo binaries in ~/.mongodb-prebuilt - // After that startup MongoDB instance - const startupPromise = mongodCli.run(); - this.constructor.mongodHelperStartup = startupPromise; - await startupPromise; - - data.mongodCli = mongodCli; - data.tmpDir = tmpDir; - - return data; - } - - async stop(): Promise { - const { mongodCli, port, tmpDir } = (await this.getInstanceData(): MongoInstanceDataT); - - if (mongodCli && mongodCli.mongoBin.childProcess) { - // .mongoBin.childProcess.connected - this.debug( - `Shutdown MongoDB server on port ${port} with pid ${mongodCli.mongoBin.childProcess.pid}` - ); - mongodCli.mongoBin.childProcess.kill(); - } - - if (tmpDir) { - this.debug(`Removing tmpDir ${tmpDir.name}`); - tmpDir.removeCallback(); - } - - this.runningInstance = null; - return true; - } - - async getInstanceData(): Promise { - if (this.runningInstance) { - return this.runningInstance; - } - throw new Error( - 'Database instance is not running. You should start database by calling start() method. BTW it should start automatically if opts.autoStart!=false. Also you may provide opts.debug=true for more info.' - ); - } - - async getUri(otherDbName?: string | boolean = false): Promise { - const { uri, port } = (await this.getInstanceData(): MongoInstanceDataT); - - // IF true OR string - if (otherDbName) { - if (typeof otherDbName === 'string') { - // generate uri with provided DB name on existed DB instance - return generateConnectionString(port, otherDbName); - } - // generate new random db name - return generateConnectionString(port); - } - - return uri; - } - - async getConnectionString(otherDbName: string | boolean = false): Promise { - return this.getUri(otherDbName); - } - - async getPort(): Promise { - const { port } = (await this.getInstanceData(): MongoInstanceDataT); - return port; - } - - async getDbPath(): Promise { - const { dbPath } = (await this.getInstanceData(): MongoInstanceDataT); - return dbPath; - } -} +export default MongoMemoryServer; +export { MongoMemoryServer, MongoInstance, MongoBinary }; diff --git a/src/util/MongoBinary.js b/src/util/MongoBinary.js new file mode 100644 index 000000000..598a70ea2 --- /dev/null +++ b/src/util/MongoBinary.js @@ -0,0 +1,76 @@ +/* @flow */ + +import { MongoDBDownload } from 'mongodb-download'; +import glob from 'glob'; +import os from 'os'; +import path from 'path'; + +export type MongoBinaryCache = { + [version: string]: Promise, +}; + +export type MongoBinaryOpts = { + version?: string, + downloadDir?: string, + platform?: string, + arch?: string, + http?: any, + debug?: boolean, +}; + +export default class MongoBinary { + static cache: MongoBinaryCache = {}; + + static getPath(opts?: MongoBinaryOpts = {}): Promise { + const { + downloadDir = path.resolve(os.homedir(), '.mongodb-prebuilt'), + platform = os.platform(), + arch = os.arch(), + version = '3.4.4', + http = {}, + } = opts; + + if (!this.cache[version]) { + const downloader = new MongoDBDownload({ + downloadDir, + platform, + arch, + version, + http, + }); + + if (opts.debug) { + downloader.debug = console.log.bind(null); + } + + this.cache[version] = downloader.downloadAndExtract().then(releaseDir => { + return this.findBinPath(releaseDir); + }); + } + return this.cache[version]; + } + + static findBinPath(releaseDir: string): Promise { + return new Promise((resolve, reject) => { + glob(`${releaseDir}/*/bin`, {}, (err: any, files: string[]) => { + if (err) { + reject(err); + } else if (this.hasValidBinPath(files) === true) { + const resolvedBinPath: string = files[0]; + resolve(resolvedBinPath); + } else { + reject(`path not found`); + } + }); + }); + } + + static hasValidBinPath(files: string[]): boolean { + if (files.length === 1) { + return true; + } else if (files.length > 1) { + return false; + } + return false; + } +} diff --git a/src/util/MongoInstance.js b/src/util/MongoInstance.js new file mode 100644 index 000000000..082459f0f --- /dev/null +++ b/src/util/MongoInstance.js @@ -0,0 +1,145 @@ +/* @flow */ +/* eslint-disable class-methods-use-this */ + +import { ChildProcess, spawn as spawnChild } from 'child_process'; +import path from 'path'; +import MongoBinary from './MongoBinary'; + +import type { MongoBinaryOpts } from './MongoBinary'; + +export type MongodOps = { + // instance options + instance: { + port: number, + storageEngine?: string, + dbPath: string, + debug?: boolean, + }, + + // mongo binary options + binary?: MongoBinaryOpts, + + // child process spawn options + spawn?: { + cwd?: string, + env?: Object, + argv0?: string, + stdio?: string | Array, + detached?: boolean, + uid?: number, + gid?: number, + shell?: boolean | string, + }, + + debug?: boolean, +}; + +export default class BinRunner { + static childProcessList: ChildProcess[] = []; + opts: MongodOps; + debug: Function; + + childProcess: ChildProcess; + killerProcess: ChildProcess; + instanceReady: Function; + instanceFailed: Function; + + static run(opts: MongodOps): Promise { + const instance = new this(opts); + return instance.run(); + } + + constructor(opts: MongodOps) { + this.opts = opts; + + if (this.opts.debug) { + if (!this.opts.instance) this.opts.instance = {}; + if (!this.opts.binary) this.opts.binary = {}; + this.opts.instance.debug = this.opts.debug; + this.opts.binary.debug = this.opts.debug; + } + + this.debug = this.opts.instance && this.opts.instance.debug ? console.log.bind(null) : () => {}; + } + + prepareCommandArgs(): string[] { + const { port, storageEngine, dbPath } = this.opts.instance; + + const result = []; + if (port) result.push('--port', port.toString()); + if (storageEngine) result.push('--storageEngine', storageEngine); + if (dbPath) result.push('--dbpath', dbPath); + result.push('--noauth'); + + return result; + } + + async run(): Promise { + const launch = new Promise((resolve, reject) => { + this.instanceReady = resolve; + this.instanceFailed = err => { + if (this.killerProcess) this.killerProcess.kill(); + reject(err); + }; + }); + + const mongoBin = path.resolve(await MongoBinary.getPath(this.opts.binary), 'mongod'); + this.childProcess = this.launchMongod(mongoBin); + this.killerProcess = this.launchKiller(process.pid, this.childProcess.pid); + + await launch; + return this.childProcess; + } + + launchMongod(mongoBin: string): ChildProcess { + const childProcess = spawnChild(mongoBin, this.prepareCommandArgs(), this.opts.spawn); + childProcess.stderr.on('data', this.stderrHandler.bind(this)); + childProcess.stdout.on('data', this.stdoutHandler.bind(this)); + childProcess.on('close', this.closeHandler.bind(this)); + childProcess.on('error', this.errorHandler.bind(this)); + + return childProcess; + } + + launchKiller(parentPid: number, childPid: number): ChildProcess { + // spawn process which kills itself and mongo process if current process is dead + const killer = spawnChild(process.argv[0], [ + path.resolve(__dirname, 'mongo_killer.js'), + parentPid.toString(), + childPid.toString(), + ]); + + return killer; + } + + errorHandler(err: string): void { + this.instanceFailed(err); + } + + closeHandler(code: number): void { + this.debug(`CLOSE: ${code}`); + } + + stderrHandler(message: string | Buffer): void { + this.debug(`STDERR: ${message.toString()}`); + } + + stdoutHandler(message: string | Buffer): void { + this.debug(`${message.toString()}`); + + const log: string = message.toString(); + if (/waiting for connections on port/i.test(log)) { + this.instanceReady(true); + } else if (/addr already in use/i.test(log)) { + this.instanceFailed(`Port ${this.opts.instance.port} already in use`); + } else if (/mongod instance already running/i.test(log)) { + this.instanceFailed('Mongod already running'); + } else if (/permission denied/i.test(log)) { + this.instanceFailed('Mongod permission denied'); + } else if (/Data directory .*? not found/i.test(log)) { + this.instanceFailed('Data directory not found'); + } else if (/shutting down with code/i.test(log)) { + this.instanceFailed('Mongod shutting down'); + } + } +} diff --git a/src/util/__tests__/MongoBinary-test.js b/src/util/__tests__/MongoBinary-test.js new file mode 100644 index 000000000..e515e875d --- /dev/null +++ b/src/util/__tests__/MongoBinary-test.js @@ -0,0 +1,35 @@ +/* @flow */ + +import tmp from 'tmp'; +import MongoBinary from '../MongoBinary'; + +tmp.setGracefulCleanup(); +jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + +describe('MongoBinary', () => { + it('should download binary and keep it in cache', async () => { + const tmpDir = tmp.dirSync({ prefix: 'mongo-mem-bin-', unsafeCleanup: true }); + + // download + const version = 'latest'; + const binPath = await MongoBinary.getPath({ + downloadDir: tmpDir.name, + version, + }); + // eg. /tmp/mongo-mem-bin-33990ScJTSRNSsFYf/mongodb-download/a811facba94753a2eba574f446561b7e/mongodb-macOS-x86_64-3.5.5-13-g00ee4f5/ + expect(binPath).toMatch(/mongo-mem-bin-.*\/mongodb.*\/bin$/); + + // reuse cache + expect(MongoBinary.cache[version]).toBeDefined(); + expect(MongoBinary.cache[version]).toBeInstanceOf(Promise); + await expect(MongoBinary.cache.latest).resolves.toEqual(binPath); + + // cleanup + tmpDir.removeCallback(); + }); + + it('should use cache', async () => { + MongoBinary.cache['3.4.2'] = Promise.resolve('/bin/mongod'); + await expect(MongoBinary.getPath({ version: '3.4.2' })).resolves.toEqual('/bin/mongod'); + }); +}); diff --git a/src/util/__tests__/MongoInstance-test.js b/src/util/__tests__/MongoInstance-test.js new file mode 100644 index 000000000..d584d9247 --- /dev/null +++ b/src/util/__tests__/MongoInstance-test.js @@ -0,0 +1,63 @@ +/* @flow */ + +import tmp from 'tmp'; +import MongoInstance from '../MongoInstance'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; + +let tmpDir; +beforeEach(() => { + tmp.setGracefulCleanup(); + tmpDir = tmp.dirSync({ prefix: 'mongo-mem-', unsafeCleanup: true }); +}); + +afterEach(() => { + tmpDir.removeCallback(); +}); + +describe('MongoInstance', () => { + it('should prepare command args', () => { + const inst = new MongoInstance({ + instance: { + port: 27333, + dbPath: '/data', + storageEngine: 'ephemeralForTest', + }, + }); + expect(inst.prepareCommandArgs()).toEqual([ + '--port', + '27333', + '--storageEngine', + 'ephemeralForTest', + '--dbpath', + '/data', + '--noauth', + ]); + }); + + it('should start instance on port 27333', async () => { + const mongod = await MongoInstance.run({ + instance: { port: 27333, dbPath: tmpDir.name }, + binary: { version: '3.4.4' }, + }); + + expect(mongod.pid).toBeGreaterThan(0); + mongod.kill(); + }); + + it('should throw error if port is busy', async () => { + const mongod = await MongoInstance.run({ + instance: { port: 27444, dbPath: tmpDir.name }, + binary: { version: '3.4.4' }, + }); + + await expect( + MongoInstance.run({ + instance: { port: 27444, dbPath: tmpDir.name }, + binary: { version: '3.4.4' }, + }) + ).rejects.toBeDefined(); + + mongod.kill(); + }); +}); diff --git a/src/util/mongo_killer.js b/src/util/mongo_killer.js new file mode 100644 index 000000000..313040d4e --- /dev/null +++ b/src/util/mongo_killer.js @@ -0,0 +1,33 @@ +/* eslint-disable */ +/* + make sure every few seconds that parent is still alive + and if it is dead, we will kill child too. + this is to ensure that exits done via kill + wont leave mongod around +*/ + +var parentPid = parseInt(process.argv[2], 10); +var childPid = parseInt(process.argv[3], 10); + +if (parentPid && childPid) { + setInterval(() => { + // if parent dead + try { + process.kill(parentPid, 0); + } catch (e) { + try { + process.kill(childPid); + } catch (ee) { + // doesnt matter if it is dead + } + process.exit(); + } + + // if child dead + try { + process.kill(childPid, 0); + } catch (e) { + process.exit(); + } + }, 2000); +} diff --git a/yarn.lock b/yarn.lock index 7dee98acf..00f570a5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1058,7 +1058,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.4.7, concat-stream@^1.5.2: +concat-stream@^1.5.2: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -1171,11 +1171,11 @@ dateformat@^1.0.11: get-stdin "^4.0.1" meow "^3.3.0" -debug@2, debug@^2.1.1, debug@^2.2.0, debug@^2.6.3: - version "2.6.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.6.tgz#a9fa6fbe9ca43cf1e79f73b75c0189cbb7d6db5a" +debug@2, debug@^2.1.1, debug@^2.2.0, debug@^2.6.3, debug@^2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: - ms "0.7.3" + ms "2.0.0" debug@2.2.0: version "2.2.0" @@ -1932,14 +1932,14 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.2" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" @@ -2911,7 +2911,7 @@ mime@^1.2.11: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3: +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -2951,16 +2951,6 @@ mongodb-download@^2.2.3: request-promise "^4.1.1" yargs "^3.26.0" -mongodb-prebuilt@^6.3.3: - version "6.3.3" - resolved "https://registry.yarnpkg.com/mongodb-prebuilt/-/mongodb-prebuilt-6.3.3.tgz#f1a17b34586c6739c6bc6dda196864e0464a836c" - dependencies: - debug "^2.2.0" - glob "^7.1.1" - mongodb-download "^2.2.3" - spawn-sync "1.0.15" - yargs "^3.26.0" - mongodb@^2.2.28: version "2.2.28" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.28.tgz#d8ff45754366e03973fa259bf4f11447858da657" @@ -2973,9 +2963,9 @@ ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" -ms@0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff" +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" mute-stream@0.0.5: version "0.0.5" @@ -3201,10 +3191,6 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" -os-shim@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" - os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3870,13 +3856,6 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" -spawn-sync@1.0.15: - version "1.0.15" - resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" - dependencies: - concat-stream "^1.4.7" - os-shim "^0.1.2" - spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"