Skip to content

Commit 89a6b73

Browse files
jtbairdsrnodkz
authored andcommitted
feat: add SYSTEM_BINARY option
* test on CI * fix unit test * fix prettier * check that system binary exists * fix unit tests * refactor getPath method to consume several smaller methods * update tests to excersize each smaller method individually this also updates tests to use a mocked version of the MongoBinaryDownloader so that we have a truelly issolated unit test... * fix flow typing issues * check system binary version against requested version this is a fairly simple check and doesn't attempt to do any complex version matching or resolution. It simply takes the value provided by the system and checks strict equality with the version requested. * update the README.md to include new option also added a section explaining how to use mongodb-memory-server on a system not officially supported by mongodb * check that systemBinary exists before checking versions
1 parent ff9d7b0 commit 89a6b73

File tree

4 files changed

+196
-82
lines changed

4 files changed

+196
-82
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const mongod = new MongoMemoryServer({
6363
arch?: string, // by default os.arch()
6464
debug?: boolean, // by default false
6565
skipMD5?: boolean, // by default false OR process.env.MONGOMS_SKIP_MD5_CHECK
66+
systemBinary?: string, // by default undefined or process.env.MONGOMS_SYSTEM_BINARY
6667
},
6768
debug?: boolean, // by default false
6869
autoStart?: boolean, // by default true
@@ -77,6 +78,7 @@ MONGOMS_VERSION=3
7778
MONGOMS_DEBUG=1 # also available case-insensitive values: "on" "yes" "true"
7879
MONGOMS_DOWNLOAD_MIRROR=url # your mirror url to download the mongodb binary
7980
MONGOMS_DISABLE_POSTINSTALL=1 # if you want to skip download binaries on `npm i` command
81+
MONGOMS_SYSTEM_BINARY=/usr/local/bin/mongod # if you want to use an existing binary already on your system.
8082
MONGOMS_SKIP_MD5_CHECK=1 # if you want to skip MD5 check of downloaded binary.
8183
# Passed constructor parameter `binary.skipMD5` has higher priority.
8284
```
@@ -333,6 +335,11 @@ Additional examples of Jest tests:
333335
### AVA test runner
334336
For AVA written [detailed tutorial](https://github.com/zellwk/ava/blob/8b7ccba1d80258b272ae7cae6ba4967cd1c13030/docs/recipes/endpoint-testing-with-mongoose.md) how to test mongoose models by @zellwk.
335337
338+
### Docker Alpine
339+
There isn't currently an official MongoDB release for alpine linux. This means that we can't pull binaries for Alpine
340+
(or any other platform that isn't officially supported by MongoDB), but you can use a Docker image that already has mongod
341+
built in and then set the MONGOMS_SYSTEM_BINARY variable to point at that binary. This should allow you to use
342+
mongodb-memory-server on any system on which you can install mongod.
336343
337344
## Travis
338345

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"@babel/runtime": "^7.2.0",
6262
"debug": "^4.1.0",
6363
"decompress": "^4.2.0",
64+
"dedent": "^0.7.0",
6465
"find-cache-dir": "^2.0.0",
6566
"get-port": "^4.0.0",
6667
"getos": "^3.1.1",

src/util/MongoBinary.js

Lines changed: 115 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import path from 'path';
66
import LockFile from 'lockfile';
77
import mkdirp from 'mkdirp';
88
import findCacheDir from 'find-cache-dir';
9+
import { execSync } from 'child_process';
10+
import dedent from 'dedent';
911
import MongoBinaryDownload from './MongoBinaryDownload';
1012

1113
export type MongoBinaryCache = {
@@ -22,6 +24,83 @@ export type MongoBinaryOpts = {
2224

2325
export default class MongoBinary {
2426
static cache: MongoBinaryCache = {};
27+
static debug: Function;
28+
29+
static async getSystemPath(systemBinary: string): Promise<string> {
30+
let binaryPath: string = '';
31+
32+
try {
33+
await fs.access(systemBinary);
34+
35+
this.debug(`MongoBinary: found sytem binary path at ${systemBinary}`);
36+
binaryPath = systemBinary;
37+
} catch (err) {
38+
this.debug(`MongoBinary: can't find system binary at ${systemBinary}`);
39+
}
40+
41+
return binaryPath;
42+
}
43+
44+
static async getCachePath(version: string) {
45+
this.debug(`MongoBinary: found cached binary path for ${version}`);
46+
return this.cache[version];
47+
}
48+
49+
static async getDownloadPath(options: any): Promise<string> {
50+
const { downloadDir, platform, arch, version } = options;
51+
52+
// create downloadDir if not exists
53+
await new Promise((resolve, reject) => {
54+
mkdirp(downloadDir, err => {
55+
if (err) reject(err);
56+
else resolve();
57+
});
58+
});
59+
60+
const lockfile = path.resolve(downloadDir, `${version}.lock`);
61+
62+
// wait lock
63+
await new Promise((resolve, reject) => {
64+
LockFile.lock(
65+
lockfile,
66+
{
67+
wait: 120000,
68+
pollPeriod: 100,
69+
stale: 110000,
70+
retries: 3,
71+
retryWait: 100,
72+
},
73+
err => {
74+
if (err) reject(err);
75+
else resolve();
76+
}
77+
);
78+
});
79+
80+
// again check cache, maybe other instance resolve it
81+
if (!this.cache[version]) {
82+
const downloader = new MongoBinaryDownload({
83+
downloadDir,
84+
platform,
85+
arch,
86+
version,
87+
});
88+
89+
downloader.debug = this.debug;
90+
this.cache[version] = await downloader.getMongodPath();
91+
}
92+
93+
// remove lock
94+
LockFile.unlock(lockfile, err => {
95+
this.debug(
96+
err
97+
? `MongoBinary: Error when removing download lock ${err}`
98+
: `MongoBinary: Download lock removed`
99+
);
100+
});
101+
102+
return this.cache[version];
103+
}
25104

26105
static async getPath(opts?: MongoBinaryOpts = {}): Promise<string> {
27106
const legacyDLDir = path.resolve(os.homedir(), '.mongodb-binaries');
@@ -43,84 +122,61 @@ export default class MongoBinary {
43122
platform: process.env?.MONGOMS_PLATFORM || os.platform(),
44123
arch: process.env?.MONGOMS_ARCH || os.arch(),
45124
version: process.env?.MONGOMS_VERSION || 'latest',
125+
systemBinary: process.env?.MONGOMS_SYSTEM_BINARY,
46126
debug:
47127
typeof process.env.MONGOMS_DEBUG === 'string'
48128
? ['1', 'on', 'yes', 'true'].indexOf(process.env.MONGOMS_DEBUG.toLowerCase()) !== -1
49129
: false,
50130
};
51131

52-
let debug;
53132
if (opts.debug) {
54133
if (typeof opts.debug === 'function' && opts.debug.apply && opts.debug.call) {
55134
debug = opts.debug;
56135
} else {
57-
debug = console.log.bind(null);
136+
this.debug = console.log.bind(null);
58137
}
59138
} else {
60-
debug = (msg: string) => {}; // eslint-disable-line
139+
this.debug = (msg: string) => {}; // eslint-disable-line
61140
}
62141

63142
const options = { ...defaultOptions, ...opts };
64-
debug(`MongoBinary options: ${JSON.stringify(options)}`);
65-
66-
const { downloadDir, platform, arch, version } = options;
67-
68-
if (this.cache[version]) {
69-
debug(`MongoBinary: found cached binary path for ${version}`);
70-
} else {
71-
// create downloadDir if not exists
72-
await new Promise((resolve, reject) => {
73-
mkdirp(downloadDir, err => {
74-
if (err) reject(err);
75-
else resolve();
76-
});
77-
});
78-
79-
const lockfile = path.resolve(downloadDir, `${version}.lock`);
80-
81-
// wait lock
82-
await new Promise((resolve, reject) => {
83-
LockFile.lock(
84-
lockfile,
85-
{
86-
wait: 120000,
87-
pollPeriod: 100,
88-
stale: 110000,
89-
retries: 3,
90-
retryWait: 100,
91-
},
92-
err => {
93-
if (err) reject(err);
94-
else resolve();
95-
}
96-
);
97-
});
98-
99-
// again check cache, maybe other instance resolve it
100-
if (!this.cache[version]) {
101-
const downloader = new MongoBinaryDownload({
102-
downloadDir,
103-
platform,
104-
arch,
105-
version,
106-
});
107-
108-
downloader.debug = debug;
109-
this.cache[version] = await downloader.getMongodPath();
143+
this.debug(`MongoBinary options: ${JSON.stringify(options)}`);
144+
145+
const { version, systemBinary } = options;
146+
147+
let binaryPath: string = '';
148+
149+
if (systemBinary) {
150+
binaryPath = await this.getSystemPath(systemBinary);
151+
if (binaryPath) {
152+
const binaryVersion = execSync('mongod --version')
153+
.toString()
154+
.split('\n')[0]
155+
.split(' ')[2];
156+
157+
if (version !== 'latest' && version !== binaryVersion) {
158+
// we will log the version number of the system binary and the version requested so the user can see the difference
159+
this.debug(dedent`
160+
MongoMemoryServer: Possible version conflict
161+
SystemBinary version: ${binaryVersion}
162+
Requested version: ${version}
163+
164+
Using SystemBinary!
165+
`);
166+
}
110167
}
168+
}
111169

112-
// remove lock
113-
LockFile.unlock(lockfile, err => {
114-
debug(
115-
err
116-
? `MongoBinary: Error when removing download lock ${err}`
117-
: `MongoBinary: Download lock removed`
118-
);
119-
});
170+
if (!binaryPath) {
171+
binaryPath = await this.getCachePath(version);
120172
}
121173

122-
debug(`MongoBinary: Mongod binary path: ${this.cache[version]}`);
123-
return this.cache[version];
174+
if (!binaryPath) {
175+
binaryPath = await this.getDownloadPath(options);
176+
}
177+
178+
this.debug(`MongoBinary: Mongod binary path: ${binaryPath}`);
179+
return binaryPath;
124180
}
125181

126182
static hasValidBinPath(files: string[]): boolean {
Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,89 @@
11
/* @flow */
22

33
import tmp from 'tmp';
4+
import fs from 'fs';
5+
import os from 'os';
46
import MongoBinary from '../MongoBinary';
57

8+
const MongoBinaryDownload: any = require('../MongoBinaryDownload');
9+
610
tmp.setGracefulCleanup();
711
jasmine.DEFAULT_TIMEOUT_INTERVAL = 600000;
812

13+
const mockGetMongodPath = jest.fn().mockResolvedValue('/temp/path');
14+
15+
jest.mock('../MongoBinaryDownload', () => {
16+
return jest.fn().mockImplementation(() => {
17+
return { getMongodPath: mockGetMongodPath };
18+
});
19+
});
20+
921
describe('MongoBinary', () => {
10-
it('should download binary and keep it in cache', async () => {
11-
const tmpDir = tmp.dirSync({ prefix: 'mongo-mem-bin-', unsafeCleanup: true });
12-
13-
// download
14-
const version = 'latest';
15-
const binPath = await MongoBinary.getPath({
16-
downloadDir: tmpDir.name,
17-
version,
22+
let tmpDir;
23+
24+
beforeEach(() => {
25+
tmpDir = tmp.dirSync({ prefix: 'mongo-mem-bin-', unsafeCleanup: true });
26+
});
27+
28+
// cleanup
29+
afterEach(() => {
30+
tmpDir.removeCallback();
31+
MongoBinaryDownload.mockClear();
32+
mockGetMongodPath.mockClear();
33+
MongoBinary.cache = {};
34+
});
35+
36+
describe('getPath', () => {
37+
it('should get system binary from the environment', async () => {
38+
const accessSpy = jest.spyOn(fs, 'access');
39+
process.env.MONGOMS_SYSTEM_BINARY = '/usr/local/bin/mongod';
40+
await MongoBinary.getPath();
41+
42+
expect(accessSpy).toHaveBeenCalledWith('/usr/local/bin/mongod');
43+
44+
accessSpy.mockClear();
1845
});
19-
// eg. /tmp/mongo-mem-bin-33990ScJTSRNSsFYf/mongodb-download/a811facba94753a2eba574f446561b7e/mongodb-macOS-x86_64-3.5.5-13-g00ee4f5/
20-
expect(binPath).toMatch(/mongo-mem-bin-.*\/.*\/mongod$/);
21-
22-
// reuse cache
23-
expect(MongoBinary.cache[version]).toBeDefined();
24-
expect(MongoBinary.cache[version]).toEqual(binPath);
25-
const binPathAgain = await MongoBinary.getPath({
26-
downloadDir: tmpDir.name,
27-
version,
46+
});
47+
48+
describe('getDownloadPath', () => {
49+
it('should download binary and keep it in cache', async () => {
50+
// download
51+
const version = 'latest';
52+
const binPath = await MongoBinary.getPath({
53+
downloadDir: tmpDir.name,
54+
version,
55+
});
56+
57+
// eg. /tmp/mongo-mem-bin-33990ScJTSRNSsFYf/mongodb-download/a811facba94753a2eba574f446561b7e/mongodb-macOS-x86_64-3.5.5-13-g00ee4f5/
58+
expect(MongoBinaryDownload).toHaveBeenCalledWith({
59+
downloadDir: tmpDir.name,
60+
platform: os.platform(),
61+
arch: os.arch(),
62+
version,
63+
});
64+
65+
expect(mockGetMongodPath).toHaveBeenCalledTimes(1);
66+
67+
expect(MongoBinary.cache[version]).toBeDefined();
68+
expect(MongoBinary.cache[version]).toEqual(binPath);
2869
});
29-
expect(binPathAgain).toEqual(binPath);
70+
});
3071

31-
// cleanup
32-
tmpDir.removeCallback();
72+
describe('getCachePath', () => {
73+
it('should get the cache', async () => {
74+
MongoBinary.cache['3.4.2'] = '/bin/mongod';
75+
await expect(MongoBinary.getCachePath('3.4.2')).resolves.toEqual('/bin/mongod');
76+
});
3377
});
3478

35-
it('should use cache', async () => {
36-
MongoBinary.cache['3.4.2'] = '/bin/mongod';
37-
await expect(MongoBinary.getPath({ version: '3.4.2' })).resolves.toEqual('/bin/mongod');
79+
describe('getSystemPath', () => {
80+
it('should use system binary if option is passed.', async () => {
81+
const accessSpy = jest.spyOn(fs, 'access');
82+
await MongoBinary.getSystemPath('/usr/bin/mongod');
83+
84+
expect(accessSpy).toHaveBeenCalledWith('/usr/bin/mongod');
85+
86+
accessSpy.mockClear();
87+
});
3888
});
3989
});

0 commit comments

Comments
 (0)